Introduction
Report bugs and suggest features
If you think you've found a bug to report in Harlowe, want to make a feature suggestion, or wish to see what future features are already planned, simply visit the project's issues page.
I'd appreciate it if you could adhere to the following guidelines when reporting bugs or proposals:
- Please try to check that the bug has not been reported before, using the issue search feature.
- Please do not report more than one bug per issue - make as many issues as you need.
- Please be as detailed as possible in the issue title.
- But, at the same time, avoid overly general anecdotes or descriptions of your work process leading to the bug (like "I'm trying to write an inventory system") - try to focus on what you expected the code to do, and what you got instead.
- You should report all cases where a new version of Harlowe caused your formerly-working story to suddenly not work, as long as a possible reason was not mentioned anywhere in the changelog.
- Do include code samples, but please write them inside code blocks (using the Code
</>
button in the toolbar). - Don't change the "Assignee", "Milestone" or "Due Date".
- If your issue is a bug, please set the "Label" to "Bug".
- You may suggest feature requests, but please set the issue's "Label" to "Proposal".
File reports for this documentation, too!
Harlowe is maintained by one person (me) out of a single interactive fiction language development bedroom. As such, I only have a limited number of staff to keep watch over the documentation and the program. I appreciate all reports about documentation slip-ups, typos, outdated advice, flat-out incorrect examples, and so forth.
A brief note of thanks
As of July 2020, Harlowe's issues page and hosting is now at foss.heptapod.net. Big thanks to Octobus SAS for providing this free open-source software hosting service, and Clever Cloud SAS for that service's hosting.
Unstable 4.0 builds now available!
By popular demand: you can now access the latest in-progress build of Harlowe 4.0, for your own personal use as an installable story format in Twine. Be warned that Harlowe 4 has intentional incompatibility with Harlowe 3 code, Harlowe 3 CSS, and Harlowe 3 browser support, and that all use of these builds is entirely without warranty! Please scrutinise the change log to fully understand what changes Harlowe 4 brings.
Click here to get the unstable build (last updated 2024-01-13)
Unstable builds are updated at least within a week of all new developments.
What Harlowe does best
Harlowe is one of a small handful of story formats offered in Twine. Each has its own specific focus and audience. Harlowe is designed for the following kind of author and work.
No HTML, Javascript or CSS experience needed
At its core, Harlowe's language is designed to assist authors with no familiarity with HTML, Javascript or CSS. Rather than requiring knowledge of all three languages, Harlowe provides a single language that fulfills the basic needs of all three and whose parts seamlessly integrate.
Use layout syntax, such as columns and aligners, to provide structure and composition to your prose. Harlowe contains special values that represent one or more CSS styles bundled together for convenience. Attach them to single runs of prose to provide the equivalent of inline styles, or use variables or the (change:) or (enchant:) macros to globally affect certain labeled structures. Harlowe's coding language, inside macro calls, smooths over the pitfalls and discrepancies of Javascript - better type-checking and clearer syntax prevents obvious datatype bugs like 1 + "1"
, 1 == "1"
or if (a = 1)
, more plentiful error messages replace silent failures and junk values like NaN
or undefined
, different data structures each use the same setting, getting and checking syntax, and the styling datatypes are easily handled alongside other data. Styles can be combined by simply adding them together, for instance, and computed based on the current game state.
Though, if you already have HTML, Javascript or CSS experience, and would prefer to leverage that experience as you create, you may wish to use SugarCube instead, which provides more direct access to the page's HTML elements and to the Javascript language.
Dynamic hypertext as a focus
I have a deep admiration for the storytelling potential of early and recent hypertext mediums, such as HyperCard, Shockwave, Flash, and early HTML fiction, and I have kept their versatility in mind when creating Harlowe. Harlowe heavily encourages you to think of a page as a dynamic interactive space, not just a sequence of prose followed by choices. Harlowe encourages you to place links and interactive elements in the midst of prose, not just at the end, and to use them to change the prose in surprising and unusual ways - inserting or removing text in a previously-read paragraph, changing the styling of words, changing just the link itself, and other such effects to reveal new meaning in the text and communicate your story in a manner unique to hypertext. If you would like to explore the storytelling potential of hypertext, it is my dear hope that you will find Harlowe satisfying.
Though, if you would prefer a more traditional, branching style of interactive prose writing, you may wish to use Chapbook or a non-Twine language like Ink instead.
Programming depth available when you need it
Don't let the preceding sections lead you to believe Harlowe is narrowly limited as a programming environment. Despite crafting Harlowe as a language apart from Javascript, I've nonetheless equipped it with tools and utilities to handle dense data manipulation. A wide collection of conversion macros and syntactic structures exist to convert and manipulate arrays, strings, and maps, including a lambda syntax, and the (macro:) macro lets you sculpt a personal sub-language within Harlowe. In addition, the Debug Mode gives you a live view of variables, styles, and game state as the story progresses. While these are not meant to be immediately useful to the first-time author, one who has grown more ambitious in their time with Harlowe may call upon them to make more computationally complicated stories, such as basic role-playing games. As you grow in programming confidence, Harlowe can follow you.
No specific simulation elements
Interactive fiction is commonly associated with text adventure games with a high degree of spatial simulation and procedurally generated text, where you control a player-character and manipulate objects and navigate rooms. Harlowe (and the other Twine story formats) is intended for a much wider variety of stories with a much lighter amount of interaction with the story's inner world, and as such it does not contain pre-built programming constructs for rooms, objects, inventories, manipulation verbs, and other common design affordances of text adventures. If you would prefer to write a story with a higher degree of simulation and interaction, you may wish to use a non-Twine language like Inform instead.
Three fundamentals of Harlowe
Harlowe is a markup language that you can apply to your Twine prose to make the prose interactive, non-linear, and game-like. This language's full contents of features may appear daunting, but all of its features revolve around the following three simple concepts, which, when understood, unlock understanding the rest of the language:
1. Macros produce either commands, or data values
A macro is the basic unit of code. It is either a command that changes the game's state or the prose in some way, such as the (set:) macro that saves data to a variable, or the (link-goto:) macro that puts a link inside the prose, or it produces a data value, such as a number or a styling instruction. All of Harlowe's macros are either one or the other.
(set: $companion to "Gallifrey") This is a command.
(print: $companion) This is a command.
(lowercase: "SCREECH") This is a value (a string).
(a: 3,4,5) This is a value (an array).
(for: each _num, 1,2,3) This is a value (a changer).
2. Attach values to hooks of text to change the text
To use macros to change your story's prose, you must place that prose between brackets, which make a hook. You can then attach one or more values to the front of the hook to change the prose. This syntactic structure is used for everything from text styling with (text-style:), to the narrative-branching of the (if:) macro, to more complex commands like (event:) or (for:).
(set: $ringStolen to false)
(if: $ringStolen)[The ring, as expected, is gone.]
In the above example, an "if" statement is created by attaching the (if:) changer value to the square-bracketed hook.
3. You can use anything that produces a value as if it was that value
(set: $text to "VALID")
(set: $slide to (transition:"slide-right"))
(lowercase: "VALID") This is valid.
(lowercase: "VAL" + "ID") This is valid.
(lowercase: "VAL" + (uppercase: "id")) This is valid.
(lowercase: $text) This is valid.
(transition:"slide-right")[VALID] This is valid.
$slide[VALID] This is valid.
It is important, when you read example code in this documentation, not to assume that you're restricted to very specific ways of writing macros and expressions. Just because an example uses a variable, doesn't mean you can't instead use a plain value, or a macro that produces that value, in that exact same place. Variables and macros are interchangeable with the values they contain or produce – a variable containing a number can be used anywhere that a number could be used, as can a macro that produces a number. This means you have a wide range of expressiveness in writing your story's code - you can save values into variables simply to save having to write them in full repeatedly, and you can use data-choosing macros like (either:), (cond:), (nth:) and so forth nearly anywhere you want. Use whatever form is most suited to making your prose readable!
Example stories
Here are a few example stories, written by me, Leon, and designed to be downloaded and opened in the Twine editor for reference and experimentation. These stories' prose and Harlowe code (though, of course, not the Harlowe engine itself) are entirely public domain - use their contents for your own projects as you wish.
Quack of Duckness [download].
This is a parody of the example story for the Chapbook story format, "Cloak of Darkness". This demonstrates several basic Harlowe features:
- Using variables to track the player's actions.
- Using (if:) with the "visits" keyword to display text only once.
- Using (click-append:) to add a link in the middle of a paragraph.
- Using (transition:) to alter the transition of a hook, both as an attached changer and as an additional value given to (click-append:).
- Using a boolean variable to enable a hook, similar to (if:).
- Using escaped line break syntax to control whitespace in the story.
- Using "footer" tagged passages to place text beneath every passage in the story.
- Using (enchant:) with (t8n-depart:) and (t8n-arrive:) inside a "footer" tagged passage to affect the transition of every passage link in the story.
- Using (append:) with the ?sidebar hook name to add icons to the sidebar.
Additionally, it contains an extra passage with a "character creator" board, which allows players to spend statistic points to increase character attributes, in the manner of an RPG. This board makes use of these features:
- Using named hooks and (rerun:) to alter a statistical readout whenever the player modifies one of the statistics.
- Using temp variables to store complicated commands such as (link:) that need to be used multiple times in the passage.
The Basics of TBC [download].
This demonstrates how one would implement a very simple 1-vs-1 turn-based-combat (TBC) engine in Harlowe, in the manner of an RPG. It provides examples of the following features:
- Using (click:) with ?page to only advance the story when the mouse is clicked.
- Using custom macros to reduce the amount of overall code that needs to be written.
- Using temp variables to store calculated values that are used further down the passage.
- Using datamaps (created via (dm:)) to greatly simplify the process of defining unique turn-based-combat opponents, as well as reducing the amount of variables that need to be created to store data relating to the battle's state.
- Using arrays (created via (a:)) to store sequential data, and using (move:) and (rotated:) to change the first value of those arrays.
Styling with Enchantments [download].
This demonstrates a number of ways you can style your stories without needing to use CSS stylesheets. All of the styles in this story are coded in separate passages, and their code is free to use in your stories. They provide examples of the following features:
- Using (enchant:) with ?page, ?passage and ?link to alter the visual presentation of various parts of the page.
- Using (hover-style:) to change how links appear when the mouse hovers over them.
- Using (background:) with (gradient:) and (stripes:).
- Using (hide:) and (replace:) with ?sidebar to alter the sidebar.
Enjoy the examples!
Editing and debugging
Editor toolbar
Harlowe is intended for use inside Twine 2, which installs with it included as the default format. When Harlowe is selected as the current story format for your story (via a dropdown menu in the Details tab of the Story menu), then it will augment Twine's passage editor with extra toolbar buttons. These buttons either allow markup to be quickly inserted into the passage code, or provide some other kind of assistance in editing. These buttons are outlined as follows.
- Text styles: each item in this dropdown menu (or, depending on version, set of buttons) wraps the selected text in one of the markup styles Harlowe offers. Selecting "More Styles" will produce a dialog box that allows you to mix and match a wider variety of styles, displaying a preview pane containing your chosen combination. That dialog, when OK is clicked, will wrap the selected text in a hook (or, if "affect the entire remainder of the passage or hook" was selected, adds an unclosed hook)with an attached (text-style:) macro call that enables them.
- Colour: this produces a dialog box that lets you set the text colour and opacity, as well as set the background to a flat colour or a linear gradient. Choosing OK will do one of a number of things depending on which "affect" option was selected.
- When affecting "the attached hook", it wraps the selected text in a hook with an attached (bg:) and/or (text-colour:) changer.
- When affecting "the remainder of the passage or hook", it adds an unclosed hook with an attached (bg:) and/or (text-colour:) changer.
- When affecting "the entire passage" or "the entire page", it adds an (enchant:) macro call targeting either the ?Passage or ?Page hook name, and applying a (bg:) and/or (text-colour:) changer.
- Borders: this produces a dialog box that lets you specify HTML border styles for all four sides of a block of text. Choosing OK will do one of a number of things depending on which "affect" option was selected.
- When affecting "the attached hook", it wraps the selected text in a hook with an attached (b4r:), (b4r-size:) and/or (b4r-colour:) changer.
- When affecting "the remainder of the passage or hook", it adds an unclosed hook with an attached (b4r:), (b4r-size:) and/or (b4r-colour:) changer.
- When affecting "the entire passage", it adds an (enchant:) macro call targeting the ?Passage hook name, and applying a (b4r:), (b4r-size:) and/or (b4r-colour:) changer.
- Rotated text: this produces a dialog box that lets you rotate a block of text through 3 geometric axes. Choosing OK will wrap the selected text in a hook (or, if "affect the entire remainder of the passage or hook" was selected, adds an unclosed hook) with an attached (text-rotate-x:), (text-rotate-y:) and/or (text-rotate-z:) changer.
- List and line items: the items in this dropdown menu insert syntax elements that affect a single line: the bulleted list markup, the numbered list markup, a horizontal rule, or the level 1 heading markup. These each have very simply and easy-to-memorise syntax, but this menu exists in case you forget.
- Alignment and Columns: this dropdown menu features two items.
- Alignment produces a dialog box that lets you adjust the horizontal margins, width and alignment of a block of text. Choosing OK will wrap the selected text in a hook (or, if "affect the entire remainder of the passage or hook" was selected, adds an unclosed hook) with an attached (align:) and/or (box:) changer.
- Columns produces a dialog box that lets you define two or more columns of text, each with their own width and margins. Choosing OK will insert the column markup into the passage, replacing any selected text.
- Collapse whitespace: this produces a dialog box that lets you choose between wrapping the selected text in the collapsing whitespace markup, or insert the unclosed collapsing whitespace markup. The chosen option is performed when clicking OK.
- Verbatim: this wraps the selected text in the verbatim markup. If the selected text contains any grave
`
marks, then enough`
marks will be used to unambiguously wrap it. - HTML Comments: this wraps the selected text in the HTML comments markup.
- Macro: this dropdown menu can be used to choose from a number of "wizards" (dialog forms) that help you insert specific Harlowe macros that perform a certain common task. Note that these wizards only offer a limited number of features, and that understanding the relevant Harlowe macros, and writing their calls yourself, will provide more sophisticated control over them.
- Link: this wizard lets you design a link (or specify that the page can be clicked), and the action to perform when clicking it.
- If: this wizard lets you design a condition, involving various actions the player may have performed, or the current state of a variable, that causes a hook to be shown.
- Input: this wizard lets you design either an (input-box:) command, a (force-input-box:) command, a (dropdown:) command, a (checkbox:) command, or a (dialog:) command, each of which can have their player-inputted value bound to a variable.
- Hook: this very simple dialog lets you create a named hook.
- Value: this wizard lets you design a (set:) command, which sets a single variable to a specific value.
Be aware for all of the above that, once OK is clicked and code has been placed, there isn't any way to "unmake" the code and return to the dialog that produced it. So, if you want to change some aspect of the resulting code, you'll have to edit it as-is.
The following buttons are toggleable - they enable or disable special editing modes or features.
- Proofreading View: this dims all of the passage code except for plain text, and string data. This is meant for proofreading the written prose of your passage, while reducing the distraction of colourful macro code. Because string data often carries fragments or portions of prose for display in the story (such as alternatives given to (nth:) or (either:)), those remain undimmed along with plain text. While you may wish to use the "Proof" feature in Twine 2 for more thorough proofreading, this is ideal for quicker reviews and corrections of individual passages.
- Coding Tooltips: enabled by default, this provides explanatory tooltips when you rest the editor cursor over a Harlowe macro, hook, or coding structure which isn't plain text. Once you have a decent grasp of how Harlowe code works, you can toggle these off to reduce editing distractions.
The remaining buttons offer additional editing utilities.
- Find and Replace: this opens a rudimentary find/replace dialog, such as can be found in most text editors. Entering text in the "Find" box causes all instances to be highlighted purple inside the editor. The ↑ and ↓ buttons change the "current match" (which is the match that will be replaced first using the "Replace" button) as well as scrolling it into view. You can use the dropdown to limit matches to either passage prose (to prevent macro names or keywords like
it
from being included), code (to prevent text from being included), or the current selection. Finally, you can make the search case-sensitive using a checkbox. Clicking Done will close the dialog box and remove the search highlighting. - Manual: opens this web page.
- Editor Preferences: provides options for toggling some of the Passage Editor's Harlowe-specific features on and off.
Finally, while most of these buttons don't have visible labels, hovering over one will produce a small tooltip revealing its name.
Editor keyboard shortcuts
When using the passage editor in Twine 2, the following keyboard (and mouse) shortcuts are available. Note that many of these (in particular, those involving cursor movement and selections) are shared across all Twine story formats, not just Harlowe. In the notation below, substitute Ctrl for ⌘ if you are using MacOS.
Keyboard shortcut | Purpose |
---|---|
Ctrl+Left Click | Create additional text cursors. These cursors all output the same text at their location when you type, and can be moved simultaneously. Use this feature to edit or insert the same text in multiple locations. |
Ctrl+Left Drag | Create multiple, disconnected text selections without deselecting the current selections. These can all be edited simultaneously by typing, like single selections. |
Ctrl+D | Delete the entire line that the cursor is on. |
Ctrl+Home | Move the cursor to the top of the passage. |
Ctrl+End | Move the cursor to the bottom of the passage. |
Ctrl+Left | Move the cursor to the start of the previous "clump" of characters. These are either words (clumps of alphanumeric characters), runs of punctuation, or runs of multiple whitespace characters. |
Ctrl+Right | Move the cursor to the end of the next "clump" of characters. These are either words (clumps of alphanumeric characters), runs of punctuation, or runs of multiple whitespace characters. |
Alt+Left | Move the cursor to the start of the current line. |
Alt+Right | Move the cursor to the end of the current line. |
Ctrl+B | Wrap the current selected text in the "bold" style markup. |
Ctrl+I | Wrap the current selected text in the "italic" style markup. |
Ctrl+- | Wrap the current selected text in the "strikethrough" style markup. |
Ctrl+. | Wrap the current selected text in the "superscript" style markup. |
Ctrl+F | Open the Find/Replace panel. |
Ctrl+G | Go to the next Find/Replace match (scroll it onto the screen, and make it the target of the next "Replace" operation). |
Ctrl+Shift+G | Go to the previous Find/Replace match. |
Ctrl+H | Replace the current Find/Replace match. |
In addition to these, the most basic text editor operations (like using Ctrl-Z to undo, Ctrl-Y or Ctrl-Shift-Z to redo, Ctrl+A to select all of the text, Shift+Left or Shift+Right to adjust the current selection, dragging the current selected text to reposition it, and the like) are, of course, also available.
Debug Mode
If you select "Test" or use the "Test From Here" feature in the Twine editor, or use the (debug:) macro, Harlowe will enter Debug Mode. Debug Mode provides a number of useful features for testing your story, examining how it runs, and checking what variables contain.
The exact differences between normal play and Debug Mode are as follows.
- At the start of the story, passages tagged with
debug-startup
will run, in alphabetical order by their passage name, after passages taggedstartup
. - In every passage, passages tagged with
debug-header
will be displayed, in alphabetical order by their passage name, after passages taggedheader
, and passages tagged withdebug-footer
will be displayed, in alphabetical order by their passage name, after passages taggedfooter
. - Error messages will have a 🔍 button visible on them, allowing a replay to be watched (see below). This can be toggled on and off in Debug Mode's options.
- The Debug Mode panel (depicted below) will be active.
Generally speaking, Debug Mode is not intended to be used by your story's players, and you aren't expected to distribute stories with Debug Mode enabled.
Debug Mode panel:
The Debug Mode panel sits on the lower-right corner of the browser window. It provides access to the following features.
- Resizer bar: click and drag this to resize the panel horizontally. A similar bar appears on all drawer panels, allowing you to resize them vertically.
- Variables: this button opens and closes a drawer panel that displays the contents of all of the variables (as well as individual values inside data structures) currently active in the story. This is a table with the following columns. Click any column to sort the table (alternating descending and ascending per click) by that column. At the bottom, a button marked "Copy $ variables as (set:) call" will, when clicked, copy to the Clipboard a source code representation of a (set:) call which recreates all of the story-wide variables as they are right now - which may be useful for recreating this exact variable state elsewhere in the story, for testing purposes.
- Type: lists the type that the variable has been restricted to (by using the
-type
syntax in a (set:), (put:), (unpack:) or (macro:) macro). If no restriction was made (which is the same as restricting to theany
datatype), then this is blank. - Name: the name of the variable, including its
$
or_
sigil. The sigil is not used to determine the sorting of the column. If this is a data value inside a data structure (for example,$list's 1st
), then the name of the containing variable will be grayed out, and the name (or position) of the data value will be listed after it. However, if the containing data structure is a dataset, then its name will be listed as???
(because datasets' values can't be accessed by name or position). - Scope: if the variable is a temp variable, then this lists the scope that the variable is usable. If a temp variable is created inside a hook, then the hook will be listed here - otherwise, it will say that the hook's scope is "in this passage". Sort by this column to separate temp variables from ordinary variables.
- Value: a plain-English description of the value contained in the variable, identical to that used in error messages which refer to that value. Values are colour-coded by their datatype, using identical colours to those used in Twine's passage editor.
- A blank column with (source:) buttons. These buttons will show and hide a fold-down strip that contains the source code representation of the value, as if by a (source:) or (v6m-source:) macro call.
- Type: lists the type that the variable has been restricted to (by using the
- Enchantments: this button opens and closes a drawer panel that displays all of the enchantments (created by (enchant:), (click:), or similar macros) that are currently active in the passage. This is a table with the following columns.
- Scope: the HookName (or textual string) that was given to the (enchant:) or (click:) macro, which is used to determine what structures the enchantment affects.
- Value: a plain-English description of the changer given to (enchant:). If this enchantment was created via a (click:)-related macro, this instead contains "enchanted via" and the name of the macro.
- A blank column with (source:) buttons. For enchantments created with (enchant:), these buttons will show and hide a fold-down strip that contains the source code representation of the changer given to it.
- Errors: this button opens and closes a drawer panel that lists all of the errors that have appeared in the story thus far. Keeping a close eye on this panel can be useful for finding error messages that disappeared too quickly for you to read them (because the passage contained a timed (go-to:) or (redirect:), or the error message's containing hook was replaced using (replace:) or some other macro). You may clear the panel of all errors using the "Clear this panel" button at the bottom.
- Storylets: this button only appears if at least one valid storylet (a passage containing a (storylet:) call or a specifically-written (metadata:) call) exists somewhere in the story. It opens and closes a drawer panel that displays the current status of all the storylet conditions at this present moment. This is a table with the following columns.
- Open: if the storylet condition currently produces
true
, and there are no other open storylets with higher exclusivity, then this display a tick mark. Sort by this column to easily see which storylets are currently open or not. - Name: the name of the passage.
- Condition: the lambda condition for this storylet. All such conditions are
when
lambdas, so thewhen
keyword is not displayed here. - Exclusivity: if the (exclusivity:) macro (or a specifically-written (metadata:) call) was used to change the exclusivity of the storylet, the exclusivity number is displayed here.
- Urgency: if the (urgency:) macro (or a specifically-written (metadata:) call) was used to change the urgency of the storylet, the urgency number is displayed here. Urgency only affects the sort order of the (open-storylets:) macro, and the numeric order of the (link-storylet:) macro.
- Open: if the storylet condition currently produces
- Source: this button displays the source code of the current passage, plus the source code of all
header
,footer
, and (if this is the first turn of the story)startup
tagged passages in the story, as well as debug variations of the above. Each of these passages has its own fold-down tab along the right side of the panel - use these to pick and choose what passage's source code you want to examine. (Note: as of version 3.3.4, excessively long passages will, for performance reasons, not have their code syntax-highlighted.) - Tools: this button opens and closes a drawer panel which holds a couple of special-purpose tools to help you examine the story as it is running.
- See through and click through (dialog:) boxes: turns dialog boxes (except debugging dialogs), and their dark backdrops, 90% transparent, and lets you click through them to interact with elements behind them. This can be useful when you want to proofread some text, check for error messages, or examine a replay (see below) for an element that's behind a dialog box.
- Stop links, (click:) and (hover-style:) from activating: stops clicks and cursor hovers (and any other action specified by (action:)) from activating elements (debugging elements excluded). This can be useful if you want to examine a replay (see below) inside a (click:) region or a link, or select text that's inside an
(action:"mouseover")
region. - Stop (go-to:), (undo:), (redirect:) and (refresh:) from activating: stops these commands from activating when they are added to the passage. They can be selectively activated later by turning on Debug View and clicking the "GO" buttons on them (next to the Replay button).
- Speed of timed events: a dropdown which causes a multiplier to be applied to
time
, so as to make timed events that use it occur sooner or later. It also applies a multiplier to the delay that (after:) and (live:) wait. However, it does not affect transitions at all, which is to say it has no effect on (t8n-time:) or (t8n-delay:).
- ⚙️: this button lets you configure Debug Mode's settings. These settings are saved in browser localStorage, so that they should be preserved across multiple debugging sessions. In Harlowe 3.3.0, the following settings are available.
- Debug panel is dark: changes the panel to a white-on-black colour scheme rather than black-on-white.
- Debug panel is transparent unless the cursor is over it: a feature that lets you see the passage beneath the panel as long as the mouse isn't hovering over it. You may turn this off if you find it distracting.
- Record expression replays: enables or disables replay recording (see "Debug View", below). This can hinder the performance of your story, so you may disable it if you need the story to run closer to its actual speed.
- Turns: a dropdown menu listing all of the turns that have currently elapsed in the passage, and the name of the passage visited at the start of the turn (note that the (redirect:) macro can transport the player between multiple passages during a single turn). Select a turn to undo (or redo) to that turn, as if navigating the web browser's history.
- Debug View: toggles "Debug View" (see below).
- DOM View: toggles "DOM View", a special view, intended for those familiar with HTML and CSS, that highlights and names all of the HTML DOM elements in the passage, to make it easier to examine how Harlowe's code structures are converted to HTML elements, which is useful for writing CSS rules targeting those elements.
- Close button: clicking this closes the Debug Mode panel and exits Debug Mode. Once this is done, there is no way to return to Debug Mode except via another (debug:) macro call.
Debug View:
This is a special viewing mode that makes otherwise-invisible code structures visible, and lets you examine how they were interpreted by Harlowe. When enabled, it applies the following features.
- Macro calls in the passage prose become visible, as translucent coloured rectangles containing the macro's name. These rectangles also surround any displayed output of the macro (such as that produced by (print:), (display:), (nth:) and so forth). If a replay is available (see below), a 🔍 button is also present next to the macro's name. Note that only the "bottommost" macro is visible - nested macro calls aren't made visible, but can be observed in the replay.
- Plain variables in the passage prose become visible, as translucent coloured rectangles containing the variable's name, and surrounding the variable's output.
- Hooks become visible, as tiny
[
and]
marks surrounding their contained content. If the hook is named, a tag such as<name|
appears after the closing]
mark. - Enchanted hooks (or other enchanted areas, such as links or the passage element) have a faint orange and cyan border around them.
- A few notification messages are inserted in certain places. For instance, when a hook's contents were repeated using the (for:) changer, a notification message displaying the number of loops (repetitions) is visible at the start of the hook.
Replays:
Clicking the 🔍 buttons on macro calls or error messages will produce a replay of that call, which is a dialog showing a step-by-step view of how the macro call's code was interpreted by Harlowe, starting with the initial call as written (such as (if: 2 + 5 > (max: 3, 8))
) and computing that call's values one step at a time (in this case, the steps are (if: 7 > (max: 3, 8))
, (if: 7 > 8)
and (if: false)
).
For performance reasons, Harlowe only records replay data after Debug Mode is enabled (and if the setting to record replays is enabled in Debug Mode's settings). The (debug:) macro can enable it after startup, but any macro calls that were run after the (debug:) call was run will have replay data available.
Passage markup
Link markup
Hyperlinks are the player's means of moving between passages and affecting the story. They consist of link text, which the player clicks on, and a passage name to send the player to.
Inside matching non-nesting pairs of [[
and ]]
, place the link text and the passage name,
separated by either ->
or <-
, with the arrow pointing to the passage name.
You can also write a shorthand form, where there is no <-
or ->
separator.
The entire content is treated as a passage name, and its evaluation is treated as the link text.
Example usage:
[[Go to the cellar->Cellar]] is a link that goes to a passage named "Cellar".
[[Parachuting<-Jump]] is a link that goes to a passage named "Parachuting".
[[Down the hatch]] is a link that goes to a passage named "Down the hatch".
Details:
The interior of a link (the text between [[
and ]]
) may contain any character except ]
. If additional
->
s or <-
s appear, the rightmost right arrow or leftmost left arrow is regarded as the canonical separator.
[[A->B->C->D->E]]
has a link text of "A->B->C->D" and a passage name of "E"[[A<-B<-C<-D<-E]]
has a link text of "B<-C<-D<-E" and a passage name of "A".
If the passage name of a link does not exactly match that of an existing passage, but it does if you render the markup in or around it, then Harlowe will use that name. So, you can put markup inside the link, as well as variables or value macros like (either:).
[[//Seagulls!//]]
will link to the passage named "//Seagulls!//" if it exists, or the passage named "Seagulls!" if that exists.[[Shelly?->$shellyPlace]]
is a link that goes to the passage whose name is in the variable$shellyPlace
.
However, you can't put commands or changers in the passage name. [[Really, now.->(print:$explain)]]
will cause an error.
Links can be customised by attaching changer macros, like (transition-depart:) or (text-style:). Just
place one in front of the link, like so: (t8n-depart:"dissolve")[[Recall that day]]
- or attach a variable containing
one: $memory[[Recall that day]]
. You can also customise every link in the passage using (change:) or (enchant:), and ?Link.
This syntax is not the only way to create links – there are many link macros, such as (link:), which can be used to make more versatile hyperlinks in your story.
Style markup
It's expected that you'd want to apply styles to your text – to italicise a word in dialogue, for example. You can do this with simple formatting codes that are similar to the double brackets of a link. Here is what's available to you:
Styling | Markup code | Result | HTML produced |
---|---|---|---|
Italics | //text// |
text | <i>text</i> |
Boldface | ''text'' |
text | <b>text</b> |
Strikethrough text | ~~text~~ |
<s>text</s> |
|
Emphasis | *text* |
text | <em>text</em> |
Strong emphasis | **text** |
text | <strong>text</strong> |
Superscript | meters/second^^2^^ |
meters/second2 | meters/second<sup>2</sup> |
Emphasis and strong emphasis appear identical to italics and boldface by default (though they can be changed using CSS) and are offered for those with familiarity with the Markdown language. Italics and boldface are offered for those with familiarity with SugarCube, Twine 1, or TiddlyWiki.
The alternative Markdown emphasis syntax _text_
and __text__
is not available. Harlowe reserves the use of the _
character for temp variables.
Example usage:
You //can't// be serious! I have to go through the ''whole game''
again? ^^Jeez, Louise!^^
Details:
You can nest these codes - ''//text//''
will produce bold italics - but they must nest
symmetrically. ''//text''//
will not work.
A larger variety of text styles can be produced by using the (text-style:) macro, attaching it to
a text hook you'd like to style. And, furthermore, you can use HTML tags like <mark>
as an additional
styling option.
Macro markup
A macro is a piece of code that is inserted into passage text. Macros are used to accomplish many effects, such as altering the game's state, displaying different text depending on the game's state, and altering the manner in which text is displayed.
Built in macros:
There are many built-in macros in Harlowe. To use one, you must call upon it in your passage by writing
the name, a colon, and some data values to provide it, all in parentheses. For instance, you call the (print:)
macro like so: (print: 54)
. In this example, print
is the macro's name, and 54
is the value.
The name of the macro is case-insensitive, dash-insensitive and underscore-insensitive. This means that
almost any combination of case, dashes and underscores in the name will be ignored. You can, for instance, write
(go-to:)
as (goto:)
, (Goto:)
, (GOTO:)
, (GoTo:)
, (Go_To:)
, (Got--o:)
, (-_-_g-o-t-o:)
, or
almost any other combination or variation. There is, however, ONE exception: the name cannot start with an underscore
_, because that would make it a temp variable.
Custom macros:
In addition to built-in macros, it is also possible to write your own macros, using the (macro:) macro. You need
to save these macros inside a variable or temp variable using the (set:) macro. Once you've done so, you can call it
much like it was a built-in macro, except by replacing the name with the variable: ($someCustomMacro:)
is how you would
call a custom macro stored in the variable $someCustomMacro, and (_anotherCustomMacro:)
is how you would
call a custom macro stored in the temp variable _anotherCustomMacro. Note that you can't use dataname access to
use macros that are inside arrays or datamaps: ($array's 1st:)
is, unfortunately, not a valid custom macro call.
Passing data:
You can provide any type of data values to a macro call - numbers, strings, booleans, and so forth. These
can be in any form, as well - "Red" + "belly"
is an expression that produces a single string, "Redbelly",
and can be used anywhere that the joined string can be used. Variables, too, can be used with macros, if
their contents matches what the macro expects. So, if $var
contains the string "Redbelly", then (print: $var)
,
(print: "Redbelly")
and (print: "Red" + "belly")
are exactly the same.
Furthermore, each macro call produces a value itself - (num:), for instance, produces a number, (a:) an array - so
they too can be nested inside other macro calls. (if: (num:"5") > 2)
nests the (num:) macro inside the (if:) macro.
If a macro can or should be given multiple values, separate them with commas. You can give the (a:)
macro
three numbers like so: (a: 2, 3, 4)
. The final value may have a comma after it, or it may not - (a: 2, 3, 4,)
is equally valid. Also, if you have a data value that's an array, string or dataset, you can "spread out" all
of its values into the macro call by using the ...
operator: (either: ...$array)
will act as if every value in
$array was placed in the (either:) macro call separately
Historical note:
You might notice that the majority of Harlowe macros are not, strictly speaking, macros in the computer-science sense, but are more like functions. This is purely due to historical circumstance - the original Twine 1 story format, Jonah, was based on TiddlyWiki's engine, which features parameterised transclusions called "macros". These are closer to computer-science macros in that they actually transclude markup directly into the tiddler (TiddlyWiki's term for "passage"). Thus, only Harlowe command macros like (display:) can really be considered "proper" macros.
Variable markup
As described in the documentation for the (set:) macro, variables are used to remember data values
in your game, keep track of the player's status, and so forth. They start with $
(for normal variables)
or _
(for temp variables, which only exist inside a single passage, hook or lambda).
Due to this syntax potentially conflicting with dollar values (such as $1.50) in your story text, variables cannot begin with a numeral.
You can print the contents of variables, or any further items within them, using the (print:) and (for:) macros. Or, if you only want to print a single variable, you can just enter the variable's name directly in your passage's prose.
(set: $plushieName to "Whispy", _heldItem to "briefcase")
Your beloved plushie, $plushieName, awaits you after a long work day.
You put your _heldItem down and lift it for a snuggle.
Furthermore, if the variable contains a changer command, such as that created by (text-style:) and such, then the variable can be attached to a hook to apply the changer to the hook:
(set: $robotText to (font:"Courier New"), _assistantText to (size:0.8))
$robotText[Good golly! Your flesh... it's so soft!]
_assistantText[Don't touch me, please! I'm ticklish.]
Note: While you can normally display the contents of variables by simply placing their names directly in passage prose,
such as $ship
or $crew
, you have to use another macro, such as (print:), to display the contents of arrays, datamaps, or other structures,
such as (print: $ship's mast)
or (print: $crew's 1st)
.
Note 2: Even though named hooks' names are case-insensitive, variable names are case-sensitive. So, $Chips
and $chips
are considered
different variables.
Note 3: In Harlowe 3, If you use a story-wide variable that doesn't exist (that is, it hasn't been created via (set:), (put:), and so forth), then
a default value of 0 will be used in its place. So, (print: $nonexistantVariable)
will show the text "0". This is likely to change in a future version
of Harlowe, however.
Hook markup
A hook is a means of indicating that a specific span of passage prose is special in some way. It
essentially consists of text between single [
and ]
marks. Prose inside a hook can be modified, styled,
controlled and analysed in a variety of ways using macros.
A hook by itself, such as [some text]
, is not very interesting. However, if you attach a macro or a
variable to the front, the attached value is used to change the hook in some way, such as hiding
it based on the game state, altering the styling of its text, moving its text to elsewhere in the passage.
(font: "Courier New")[This is a hook.
As you can see, this has a macro call in front of it.]
This text is outside the hook.
The (font:) macro is one of several macros which produces a special styling changer, instead of a basic data type like a number or a string. In this case, the changer changes the attached hook's font to Courier New, without modifying the other text.
You can save this changer to a variable, and then use it repeatedly, like so.
(set: $x to (font: "Tahoma"))
$x[This text is in Tahoma.]
$x[As is this text.]
The basic (if:) macro is used by attaching it to a hook, too.
(if: $x is 2)[This text is only displayed if $x is 2.]
For more information about changer macros, consult the descriptions for each of them in turn.
Named hook markup
For a general introduction to hooks, see their respective markup description. Named hooks are a less common type of hook that offer unique benefits. To produce one, attach a "nametag" to the front or back, similar to how a macro call would be attached:
[This hook is named 'opener']<opener|
|s2>[This hook is named 's2']
(Hook nametags are supposed to resemble triangular gift box nametags.)
A macro can refer to and alter the text content of a named hook by referring to the hook as if it were a variable.
To do this, write the hook's name as if it were a variable, but use the ?
symbol in place of the $
symbol:
[Fie and fuggaboo!]<shout|
(click: ?shout)[ (replace: ?shout)["Blast and damnation!"] ]
The above (click:) and (replace:) macros can remotely refer to and alter the hook using its name. This lets you, for instance, write a section of text full of tiny hooks, and then attach behaviour to them further in the passage:
Your [ballroom gown]<c1| is [bright red]<c2| with [silver streaks]<c3|,
and covered in [moonstones]<c4|.
[]<c5|
(click: ?c1)[(replace:?c5)[A hand-me-down from your great aunt.]]
(click: ?c2)[(replace:?c5)[A garish shade, to your reckoning.]]
(click: ?c3)[(replace:?c5)[Only their faint shine keeps them from being seen as grey.]]
(click: ?c4)[(replace:?c5)[Dreadfully heavy, they weigh you down and make dancing arduous.]]
As you can see, the top sentence remains mostly readable despite the fact that several words have (click:) behaviours assigned to them.
Built in names:
There are four special built-in hook names, ?Page, ?Passage, ?Sidebar, and ?Link, which, in addition to selecting named hooks, also affect parts of the page that you can't normally style with macros. They can be styled using the (change:) or (enchant:) macros.
?Page
selects the page element (to be precise, the<tw-story>
element) and using it with the (change:) and (bg:) macros lets you change the background of the entire page.?Passage
affects just the element that contains the current passage's text (to be precise, the<tw-passage>
element) and lets you, for instance, change the (text-colour:) or (font:) of all the text, or apply complex (css:) to it.?Sidebar
selects the passage's sidebar containing undo/redo icons (<tw-sidebar>
). You can style it with styling macros, or use (replace:) or (append:) to insert your own text into it.?Link
selects all of the links (passage links, and those created by (link:) and other macros) in the passage. This is similar to thelinks
data value for HookName data.
(Note that, as mentioned above, if you use these names for your own hooks, such as by creating a named hook like |passage>[]
,
then they will, of course, be included in the selections of these names.)
Hidden hook markup
Hidden hooks are an advanced kind of named hook that can be shown using macros like (show:). For a general introduction to named hooks, see their respective markup description.
There may be hooks whose contained prose you don't want to be visible as soon as the passage appears -
a time delay, or the click of a link should be used to show them. You can set a hook to be hidden by altering
the hook tag syntax - replace the >
or <
mark with a parenthesis.
|visible>[This hook is visible when the passage loads.]
|cloaked)[This hook is hidden when the passage loads, and needs a macro like `(show:?cloaked)` to reveal it.]
[My commanding officer - a war hero, and a charismatic face for the military.]<sight|
[Privately, I despise the man. His vacuous boosterism makes a mockery of my sacrifices.](thoughts|
(You can think of this as being visually similar to the pointed tails of comic speech balloons vs. round, enclosed thought balloons.)
In order to be useful, hidden hooks must have a name, which macros like (show:) can use to show them. Hence, there's no way to make a hidden unnamed hook - at least, without using a conditional macro like (if:).
Unclosed hook markup
This is a special version of the hook markup - an open bracket [
, followed by any number of =
marks, that has no matching
closing bracket. When it is placed in a passage, it indicates that all the prose that follows, until the end of the hook
that contains it or the end of the passage, is part of a single hook.
Its main purpose is to let you easily deploy hook changers that apply to the remaining text of the passage, without having to place and keep track of closing brackets at the end. For instance, the (click:) macro can be used with the ?page hook name to prompt the reader to click anywhere on the page to reveal the rest of the passage. The unclosed hook markup lets you use it as many times as you want, without needing to balance a number of closing brackets at the end of the passage.
(click: ?page)[==
This text won't appear until the page is clicked once.
(click: ?page)[==
This text won't appear until the page is clicked twice.
(click: ?page)[==
This text won't appear until the page is clicked three times.
Other changer macros, such as (link:), (more:), (event:), and (transition:), also work well with this markup.
Also, unclosed hooks can be named, and marked as hidden, just like other hooks.
|1>[=
The rest of this passage is in a hook named "1".
|2)[=
This part is also in a hidden hook named "2".
HTML markup
If you are familiar with them, HTML tags (like <img>
) and HTML escapes (like §
) can be inserted
straight into your passage text. They are treated very naively - they essentially pass through Harlowe's
markup-to-HTML conversion process untouched.
Example usage:
<mark>This is marked text.
¶ So is this.
And this.</mark>
Details:
HTML elements included in this manner are given a data-raw
attribute by Harlowe, to distinguish them
from elements created via markup.
You can include a <script>
tag in your passage to run Javascript code. The code will run as soon as the
containing passage code is rendered. See the "HTML script tag" article for more details.
You can also include a <style>
tag containing CSS code. The CSS should affect the entire page
until the element is removed from the DOM. You could use this in a "header" or "footer" tagged passage, inside an (if:) hook,
to make the CSS apply to every passage where the (if:) condition is fulfilled.
Finally, you can also include HTML comments <!-- Comment -->
in your code, if you wish to leave
reminder messages or explanations about the passage's code to yourself.
HTML script tag markup
This section details further information about the workings of <script>
elements placed inside Harlowe passages, and how the Javascript code relates to the Harlowe code in the containing passage.
If a <script>
tag has a type
attribute, and the MIME-type in that attribute is anything other than 'text/javascript', Harlowe will ignore it (in keeping with HTML's normal behaviour).
No Harlowe internal methods and modules are currently accessible inside scripts. However, jQuery 3.6 may be accessed from the $
global variable. This is a third-party library that Harlowe uses internally
and whose code is included in every compiled story, and is not retrieved via CDN or any online connections.
As of Harlowe 3.3.0, <script>
elements are run while Harlowe runs the macros and expressions in a passage. Thus, if a macro or hook is before the <script>
tag in the passage, it will be run beforehand,
and if a macro or hook is after it, it will be run after it. To defer the execution of Javascript code until the passage is fully rendered or run, consider using a setTimeout
callback.
As of Harlowe 3.3.0, Javascript code in <script>
elements (that is, without a src
attribute) has access to Harlowe variables - both story-wide variables that begin with $
, and temporary (temp) variables
that begin with _
which are visible in the same hook as the <script>
element. Harlowe variables are accessed by writing the Harlowe variable name as if it were a Javascript variable.
Valid Harlowe variable names are also coincidentally valid Javascript names, so there's no need to escape or modify them. Assigning to these names will immediately update the Harlowe variable, if possible.
Example usage:
(set: _harloweVariable to "You're reading the ")\
<script>
_harloweVariable += document.title.bold();
</script>\
(print: _harloweVariable)
Details:
To eliminate any confusion: These names are not Javascript variables, or even global window
properties, but object getters and setters added to the script's scope using a with
statement.
These names do not pollute or overwrite the global scope (although they shadow any global variables with the same names, such as window.$arr
being shadowed by a Harlowe variable $arr), and
remain accessible and current even inside a callback created within the script. Moreover, even though these names are created using a with
statement, it is still possible to opt
into Javascript's "strict mode" by placing the "use strict"
pragma at the start of the script.
If "strict mode" is not enabled for the script, an error will not occur if you attempt to assign to a Harlowe variable that doesn't exist, such as by writing $arr = []
- instead, a global
Javascript variable will be created.
Harlowe variables in Javascript currently have the following restrictions.
- Only the following six Javascript datatypes may be assigned to Harlowe variables: booleans, strings, numbers (except NaN and Infinity), Maps (created by Map() or a subclass) that have only strings as keys, Sets (created by Set() or a subclass) and Arrays. These last three data structures can only contain the aforementioned six datatypes. Attempting to assign other values will produce an error. The restriction on Map keys may be removed in a future version of Harlowe.
- Only the following Harlowe datatypes may be accessed from Javascript: numbers, booleans, strings, datamaps, datasets and arrays. These last three data structures can only contain the aforementioned six datatypes. Attempting to read other values from Harlowe variables will produce an error.
- Of course, if the Harlowe variable was type-restricted (using the
-type
syntax in a (set:), (put:) or (unpack:) macro call) then this restriction will also apply to Javascript assignments. Assigning the wrong datatype to a type-restricted Harlowe variable will cause an error. - Data structures are deep-cloned when read from Harlowe variables and assigned to Harlowe variables. Hence, Javascript code cannot receive direct references
to Harlowe values. This is to prevent Harlowe data structures from being deeply mutated (such as by calling
.set()
on a Harlowe datamap, or.push()
on a Harlowe array) as a way of circumventing the previous two restrictions. - As a consequence of the above, "expando" properties (such as
let a = []; a.pigs = 1;
) are erased from data structures, and data structures created from subclasses of Map, Set or Array will be converted to instances of the base class. - Story-wide variables that were only created after the
<script>
was run will NOT be available to the script, even if the script creates a callback function that is called later.
Finally, there is no way to call Harlowe macros from Javascript, at present. There is also no way to access identifiers like exits
or visits
from Javascript, at present.
A redundant note:
Harlowe is not meant to be a story format that you write using Javascript instead of its own language, and I have taken great care in designing it such that authors are not rewarded for knowing how to write Javascript.
The <script>
element feature is intended solely to complement existing Harlowe code, by allowing small samples of Javascript to minimally interact with it. As such, I recommend using it sparingly and judiciously.
That being said, feel free to use this feature in any way you wish, as long as you understand whether your usage lines up with its purpose and intent.
Verbatim markup
As plenty of symbols have special uses in Harlowe, you may wonder how you can use them normally, as mere symbols,
without invoking their special functionality. You can do this by placing them between a pair of `
marks.
If you want to escape a section of text which already contains single `
marks, simply increase the number
of `
marks used to enclose them.
Example usage:
I want to include `[[double square brackets]]` in my story, so I use grave ` marks.
I want to include ``single graves ` in my story``, so I place them between two grave marks.
There's no hard limit to the amount of graves you can use to enclose the text.
If you want to make an entire hook to be displayed verbatim, without its markup being rendered, you can attach the (verbatim:) changer.
Bulleted list markup
You can create bullet-point lists in your text by beginning lines with an asterisk *
, followed by whitespace,
followed by the list item text. The asterisk will be replaced with an indented bullet-point. Consecutive lines
of bullet-point items will be joined into a single list, with appropriate vertical spacing.
Remember that there must be whitespace between the asterisk and the list item text! Otherwise, this markup will conflict with the emphasis markup.
If you use multiple asterisks (**
, ***
etc.) for the bullet, you will make a nested list, which is indented deeper than
a normal list. Use nested lists for "children" of normal list items.
Example usage:
* Bulleted item
* Bulleted item 2
** Indented bulleted item
Numbered list markup
You can create numbered lists in your text, which are similar to bulleted lists, but feature numbers in place of bullets.
Simply begin single lines with 0.
, followed by whitespace, followed by the list item text. Consecutive items will be
joined into a single list, with appropriate vertical spacing. Each of the 0.
s will be replaced
with a number corresponding to the item's position in the list.
Remember that there must be whitespace between the 0.
and the list item text! Otherwise, it will be regarded as a plain
number.
If you use multiple 0.
tokens (0.0.
, 0.0.0.
etc.) for the bullet, you will make a nested list, which uses different
numbering from outer lists, and are indented deeper. Use nested lists for "children" of normal list items.
Example usage:
0. Numbered item
0. Numbered item 2
0.0. Indented numbered item
Aligner markup
An aligner is a special single-line token which specifies the alignment of the subsequent text. It is essentially
'modal' - all text from the token onward (until another aligner is encountered) is wrapped in a <tw-align>
element
(or unwrapped in the case of left-alignment, as that is the default).
- Right-alignment, resembling
==>
is produced with 2 or more=
s followed by a>
. - Left-alignment, resembling
<==
is restored with a<
followed by 2 or more=
. - Justified alignment, resembling
<==>
is produced with<
, 2 or more=
, and a closing>
. - Mixed alignment is 1 or more
=
, then><
, then 1 or more=
. The ratio of quantity of left=
s and right=
s determines the alignment: for instance, one=
to the left and three=
s to the right produces 25% left alignment.
Any amount of whitespace is permitted before or after each token, as long as it is on a single line.
Example usage:
==>
This is right-aligned
=><=
This is centered
<==>
This is justified
<==
This is left-aligned (undoes the above)
===><=
This has margins 3/4 left, 1/4 right
=><=====
This has margins 1/6 left, 5/6 right.
(Try expanding this code preview using the bar on the left.)
You may apply alignment to specific hooks in your passages by attaching the (align:) macro to them.
Column markup
Column markup is, like aligner markup, a special single-line token which indicates that the subsequent text should be laid out in columns. They consist of a number of |
marks, indicating the size of the column relative to the other columns - the total width of all columns equals the page width, and this is divided among the columns by their |
marks. They also have a number of =
marks surrounding it, indicating the size of the column's margins in CSS "em" units (which are about the width of a capital M).
All text from the token onward, until the next token is encountered, is contained in the specified column. A |==|
token ends the set of columns and returns the page to normal.
Columns are laid out from left to right, in order of appearance.
Any amount of whitespace is permitted before or after each token, as long as it is on a single line.
Example usage:
(change:?passage, (text-size:0.6))
|==
This is in the leftmost column, which has a right margin of about 2 letters wide.
=|||=
This is in the next column, which has margins of 1 letter wide. It is three times as wide as the left column.
=====||
This is in the right column, which has a right margin of about 5 letters wide. It is twice as wide as the left column.
|==|
This text is not in columns, but takes up the entire width, as usual.
(Try expanding this code preview using the bar on the left.)
You can create nested columns by enclosing the inner set of columns in an unnamed hook, like so.
(change:?passage, (text-size:0.6))
|==
This is the outer left column.
==|
This is the outer right column.
[\
|==
This is the inner left column, inside the outer right column.
==|
This is the inner right column, inside the outer right column.
\]
Heading markup
Heading markup is used to create large headings, such as in structured prose or title splash passages.
It is almost the same as the Markdown heading syntax: it starts on a fresh line,
has one to six consecutive #
s, and ends at the line break.
Example usage:
#Level 1 heading renders as an enclosing `<h1>`
###Level 3 heading renders as an enclosing `<h3>`
######Level 6 heading renders as an enclosing `<h6>`
As you can see, unlike in Markdown, opening whitespace is permitted before the first #.
Horizontal rule markup
A hr (horizontal rule) is a thin horizontal line across the entire passage. In HTML, it is a <hr>
element.
In Harlowe, it is an entire line consisting of 3 or more consecutive hyphens -
.
Example usage:
---
----
-----
Again, opening whitespace is permitted prior to the first -
and after the final -
.
Whitespace markup
"Whitespace" is a term that refers to "space" characters that you use to separate programming code tokens, such as the spacebar space, and the tab character. When used inside the macro syntax, they are considered interchangeable in type and quantity - using two spaces usually has the same effect as using one space, one tab, and so forth.
Harlowe tries to also recognise most forms of Unicode-defined whitespace, including the quads, the per-em and per-en spaces, but not the zero-width space characters (as they may cause confusion and syntax errors if unnoticed in your code).
Collapsing whitespace markup
When working with macros, HTML tags and such, it's convenient for readability purposes to space and indent
the text. However, this whitespace will also appear in the compiled passage text. You can get around this by
placing the text between {
and }
marks. Inside, all runs of consecutive whitespace (line breaks, spaces)
will be reduced to just one space.
Example usage:
{
This sentence
will be
(set: $event to true)
written on one line
with only single spaces.
}
Details:
If you wish to still have line breaks within the markup that won't be collapsed, you can use HTML <br>
tags (see
the HTML markup section for more information about raw HTML tags).
You can nest this markup within itself - {Good { gumballs!}}
- but the inner pair won't behave any
differently as a result of being nested.
Text inside macro calls (in particular, text inside strings provided to macro) will not be collapsed.
Neither will text outputted by macro calls, either - {(print:"\n\n\n")}
will still print 3 newlines
(see the String article for the meaning of the \n escape code),
and {(display:"Attic")}
will still display all of the whitespace in the "Attic" passage.
Also, newlines inside the verbatim syntax will not be collapsed either.
{Thunder`
`hound}
Note that Harlowe's default CSS already collapses consecutive spaces in a single line, but not vertical whitespace (which is converted to <br>
elements).
If you change it, using the white-space
CSS property (such as by white-space:break-spaces
in your story stylesheet),
then the effects of this syntax in removing horizontal whitespace will become noticeable.
If the markup contains a (replace:) command attached to a hook, the hook will still have its whitespace collapsed, even if it is commanded to replace text outside of the markup.
You may apply this collapsing effect to specific hooks using the (collapse:) macro. In particular, if you wish for the entire passage's whitespace to collapse, consider using (change: ?passage) and (collapse:).
If you only want to remove specific line breaks, consider the escaped line break markup.
Unclosed collapsing whitespace markup
This is a special version of the collapsing whitespace markup - an open curly brace {
, followed by any number of =
marks, that has no matching
closing brace. When it is placed in a passage, it indicates that all the prose that follows, until the end of the hook
that contains it or the end of the passage, should have its whitespace collapsed.
As with the the unclosed hook markup, this has advantages in situations where keeping track of closing brackets would be slightly inconvenient.
If you use revision macros or enchantment macros like (change:), (replace:), (click:) and so forth, you can place those at the end of
your passage, and use a single {=
to separate them from the rest of the passage. Additionally, you can place a {=
at the start of your passage
to cause the entire passage's whitespace to be collapsed, allowing you to write additional prose without needing to have a closing brace after all of
your additions.
This part of the passage
has normal whitespace.
{=
This part of the passage
has collapsed
whitespace.
All of the details pertaining to the collapsing markup apply here - consult its article for more information.
Escaped line break markup
Sometimes, you may want to write an especially long line, potentially containing many macros.
This may not be particularly readable in the passage editor, though. One piece of markup that
may help you is the \
mark - placing it just before a line break, or just after it, will cause the line break
to be removed from the passage, thus "joining together" the lines.
Example usage:
This line \
and this line
\ and this line, are actually just one line.
Details:
There must not be any whitespace between the \
and the line break. Otherwise, it won't work. (In the Twine editor, the \
will change
from yellow to gray, indicating it's no longer considered an escaped line break.)
Like most passage text markup, this cannot be used inside a macro call (for instance, (print: \
3)
) - but since line breaks between values in macro calls are ignored, this doesn't matter.
List of macros
The (set: ) macro
(set: ...VariableToValue) → Instant
Stores data values in variables, optionally allowing you to permanently restrict the variable to a single datatype.
Example usage:
(set: $battlecry to "Save a " + $favouritefood + " for me!")
creates a variable called $battlecry containing a string.(set: _dist to $altitude - $enemyAltitude)
creates a temp variable called _dist.(set: num-type $funds to 0)
sets a variable and restricts its type to numbers, preventing non-numbers from ever being (set:) into it by accident.(set: const-type $robotText to (font:"Courier New"))
sets a variable and makes it so it can't ever be set to another value.(set: (p-either:"Ms.","Mr.","Mx.")-type $charTitle to "Mx.")
sets a variable that can only hold the strings "Mr.", "Ms." or "Mx.".
Rationale:
Variables are data storage for your game. You can store data values under special names of your choosing, and refer to them later.
There are two kinds of variables. Normal variables, whose names begin with $
, persist between passages,
and should be used to store data that will be needed throughout the entire game. Temp variables,
whose names begin with _
, only exist inside the hook or passage that they're first (set:), and
are forgotten after the hook or passage ends. You should use temp variables if you're writing passage
code that mustn't accidentally affect any other passages' variables (by using (set:) on a variable name
that someone else was using for something different). This can be essential in collaborative work
with other authors working on the same story independently, or when writing code to be used in multiple stories.
The following example demonstrates where temp variables are usable.
(set: _a to 1) <- This is usable everywhere in this passage.
[
(set: _b to 1) <-- This is only usable inside this hook.
(set: _a to it + 1) <-- This changes the outer _a variable.
[
(print: _a + _b) <-- You can refer to both _a or _b in this hook.
]
]
(print: _b) <-- This will cause an error.
Variables have many purposes: keeping track of what the player has accomplished, managing some other state of the story, storing hook styles and changers, and other such things. You can display variables by putting them in passage text, attach them to hooks, and create and change them using the (set:) and (put:) macros.
Details:
Even though named hooks' names are case-insensitive, variable names are case-sensitive. So, $Chips
and $chips
are considered
different variables.
In its basic form, a variable is created or changed using (set:
variable to
value )
.
You can also set multiple variables in a single (set:) by separating each VariableToValue
with commas: (set: $weapon to 'hands', $armour to 'naked')
, etc. Note: currently,
each value in a VariableToValue is evaluated before any of them are stored in their variables.
This means that, for instance, (set: $olives to 5)(set: $olives to 2, $grapes to $olives - 1)
will,
in the second (set:) call, cause 2
and $olives - 1
to be evaluted to 2
and 5 - 1
(i.e. 4) before
being put in $olives and $grapes, respectively. This may change in a future version of Harlowe.
You can also use it
in expressions on the right-side of to
. Much as in other
expressions, it's a shorthand for what's on the left side: (set: $vases to it + 1)
is a shorthand for (set: $vases to $vases + 1)
.
Due to the variable syntax potentially conflicting with dollar values (such as $1.50) in your story text, variables cannot begin with a numeral.
Typed variables:
A common source of errors in a story is when a variable holding one type of data is mistakenly overridden
with a different type of data, such as when putting "1"
(the string "1") into a variable that should
hold numbers. A good way to guard against this is to make the variable a typed variable, which is permanently
restricted to a single datatype. The first time you set data to the variable, write (set: num-type $days to 1)
to
permanently restrict $days to numbers. That way, if you accidentally put "1"
into it, an error will appear
immediately, explaining the issue. Moreover, typed variables serve a code documentation purpose: they help
indicate and explain the purpose of a variable by showing what data is meant to be in it. You can use any
datatype before the -type
syntax - see the article about datatype data for more details.
In addition to just restricting a variable to a type, you may wish to specify that a variable should only hold one
value for the entire story - a style changer, for instance, or a datamap holding fixed values for a
procedural-generation algorithm. For these, you want to use the const
(short for "constant") datatype.
Using this, the variable is guaranteed to constantly hold that value for the entirety of the story (or, if it's
a temp variable, the passage or hook).
See also:
The (put: ) macro
(put: ...VariableToValue) → Instant
A left-to-right version of (set:) that requires the word into
rather than to
.
Example usage:
(put: "Save a " + $favouritefood + " for me!" into $battlecry)
creates a variable called $battlecry.(put: $altitude - $enemyAltitude into _dist)
creates a temp variable called _dist.(put: 0 into num-type $funds)
sets a variable and restricts its type to numbers, preventing non-numbers from ever being (put:) into it by accident.(put: (font:"Courier New") into const-type $robotText)
sets a variable and makes it so it can't ever be set to another value.
Rationale:
This macro has an identical purpose to (set:) - it creates and changes variables. For a basic explanation, see the rationale for (set:).
Almost every programming language has a (set:) construct, and most of these place the
variable on the left-hand-side. However, a minority, such as HyperTalk, place the variable
on the right. Harlowe allows both to be used, depending on personal preference. (set:) reads
as (set:
variable to
value )
, and (put:) reads as (put:
value into
variable )
.
Details:
Just as with (set:), a variable is changed using (put:
value into
variable )
. You can
also set multiple variables in a single (put:) by separating each VariableToValue
with commas: (put: 2 into $batteries, 4 into $bottles)
, etc.
You can also use typed variables with (put:) - (put: 1 into num-type $days)
permanently
restricts $days to numbers. Consult the article about (set:) for more information about
typed variables.
it
can also be used with (put:), but, interestingly, it's used on the right-hand side of
the expression: (put: $eggs + 2 into it)
.
See also:
The (move: ) macro
(move: ...VariableToValue) → Instant
A variant of (put:) that, if transferring data from a data structure, deletes the source value after copying it - in effect moving the value from the source to the destination.
Example usage:
(move: $arr's 1st into $var)
Rationale:
You'll often use data structures such as arrays or datamaps as storage for values that you'll only use once, such as a list of names to print out. When it comes time to use them, you can remove it from the structure and retrieve it in one go.
Details:
You must use the into
keyword, like (put:), with this macro. This is because, like (put:),
the destination of the value is on the right, whereas the source is on the left.
As with (set:) and (put:), you can also change multiple variables in a single (move:) by
separating each VariableToValue with commas: (move: $a's 1st into $b, $a's 2nd into $c)
, etc. Also,
unpacking syntax (described in detail in (unpack:)'s article) can be used with (move:) as well -
(move: $array into (a: $x, $y))
will cause only the first and second values of $array to be moved into $x and $y.
If the data value you're accessing cannot be removed - for instance, if it's an array's length
-
then an error will be produced.
This macro works very well with the random
data value of arrays: (move: $deck's random into $card)
will remove a random value from $deck and put it into $card. Thus, you can use arrays as random "decks"
of values that you can draw from and use once in your story.
Note that this will only delete the data from the source if the source is inside a data structure.
Moving from variable to variable, such as by (move:$p into $q)
, won't cause $p to be deleted.
Just as with (set:) or (put:), typed variables can also be used with the destination variable of (move:).
Writing (move: $enemies's 1st into dm-type $currentEnemy)
will move a datamap from $enemies's 1st and
put it into $currentEnemy, while also restricting $currentEnemy to datamap data for the rest of the story.
Note that if $enemies's 1st is not, in fact, a datamap, an error will result.
See also:
The (print: ) macro
(print: Any) → Command
This command prints out any data provided to it, as text.
Example usage:
(print: $var + "s")
Details:
It is capable of printing things which (str:) cannot convert to a string,
such as changers - but these will usually become bare descriptive
text like [A (font: ) command]
. You may find this useful for debugging purposes.
This command can be stored in a variable instead of being performed immediately. Notably, the expression to print is stored inside the command, instead of being re-evaluated when it is finally performed. So, a passage that contains:
(set: $name to "Dracula")
(set: $p to (print: "Count " + $name))
(set: $name to "Alucard")
$p
will still result in the text Count Dracula
. This is not particularly useful
compared to just setting $p
to a string, but is available nonetheless.
Note that, once stored in a variable, a (print:) command is not a string. So, you
can't provide it to (upperfirst:) and other such macros. (upperfirst: (print: $name))
will produce an error. However, if $name contains a string, you can provide it to
(upperfirst:) before giving it to (print:), such as (print: (upperfirst: $name))
.
If you need this command to print strings without the markup in the string being rendered, you may use the (verbatim:) changer to change the command, or use the (verbatim-print:) variant instead.
See also:
(str:), (display:), (verbatim-print:)
The (display: ) macro
(display: String) → Command
This command writes out the contents of the passage with the given string name. If a passage of that name does not exist, this produces an error.
Example usage:
(display: "Cellar")
prints the contents of the passage named "Cellar".
Rationale:
Suppose you have a section of code or source that you need to include in several different passages. It could be a status display, or a few lines of descriptive text. Instead of manually copy-pasting it into each passage, consider placing it all by itself in another passage, and using (display:) to place it in every passage. This gives you a lot of flexibility: you can, for instance, change the code throughout the story by just editing the displayed passage.
Details:
Text-targeting macros (such as (replace:)) inside the
displayed passage will affect the text and hooks in the outer passage
that occur earlier than the (display:) command. For instance,
if passage A contains (replace:"Prince")[Frog]
, then another passage
containing Princes(display:'A')
will result in the text Frogs
.
Like all commands, this can be set into a variable. It's not particularly
useful in that state, but you can use that variable in place of that command,
such as writing $var
in place of (display: "Yggdrasil")
.
The (if: ) macro
(if: Boolean) → Changer
This macro accepts only booleans (variables with true
or false
, or expressions using is
, contains
, or another such operator),
and produces a changer that can be attached to hooks to hide them "if" the value was false.
Example usage:
(if: $legs is 8)[You're a spider!]
will show the You're a spider!
hook if $legs
is 8
.
Otherwise, it is not run.
Rationale:
In a story with multiple paths or threads, where certain events could occur or not occur, it's common to want to run a slightly modified version of a passage reflecting the current state of the world. The (if:), (unless:), (else-if:) and (else:) macros let these modifications be switched on or off depending on variables, comparisons or calculations of your choosing.
Details:
Note that the (if:) macro only runs once, when the passage or hook containing it is rendered. Any future change to the condition (such as a (link:) containing a (set:) that changes a variable) won't cause it to "re-run", and show/hide the hook anew.
However, if you attach (if:) to a named hook, and the (if:) hides the hook, you can manually reveal
the hook later in the passage (such as, after a (link:) has been clicked) by using the (show:) macro
to target the hook. Named hooks hidden with (if:) are thus equivalent to hidden named hooks like |this)[]
.
Alternatives:
The (if:) and (hidden:) macros are not the only attachment that can hide or show hooks! In fact, a variable that contains a boolean can be used in its place. For example:
(set: $foundWand to true, $foundHat to true, $foundBeard to true)
(set: $isAWizard to $foundWand and $foundHat and $foundBeard)
$isAWizard[You wring out your beard with a quick twisting spell.]
You step into the ruined library.
$isAWizard[The familiar scent of stale parchment comforts you.]
By storing a boolean inside $isAWizard
, it can be used repeatedly throughout the story to
hide or show hooks as you please.
if you want to conditionally display very short strings, or small values inside a macro call, you may want to use the shorter (cond:) macro instead.
See also:
(unless:), (else-if:), (else:), (cond:), (show:)
The (unless: ) macro
(unless: Boolean) → Changer
This macro is the negated form of (if:): it accepts only booleans, and returns a changer that can be attached hooks to hide them "if" the value was true.
For more information, see the documentation of (if:).
Example usage:
(set: $form to "human")
(unless: $form is "duck")[The cold autumn rain chills your skin.]
The (else-if: ) macro
(else-if: Boolean) → Changer
This macro's result changes depending on whether the previous hook in the passage was shown or hidden. If the previous hook was shown, then this changer hides the attached hook. Otherwise, it acts like (if:), showing the attached hook if it's true, and hiding it if it's false. If there was no preceding hook before this, then an error message will be printed.
Example usage:
Your stomach makes {
(if: $size is 'giant')[
an intimidating rumble! You'll have to eat plenty of trees.
](else-if: $size is 'big')[
a loud growl. You're hungry for some shrubs.
](else: )[
a faint gurgle. You hope to scavenge some leaves.
]}
Rationale:
If you use the (if:) macro, you may find you commonly use it in forked branches of source: places where only one of a set of hooks should be displayed. In order to make this so, you would have to phrase your (if:) expressions as "if A happened", "if A didn't happen and B happened", "if A and B didn't happen and C happened", and so forth, in that order.
The (else-if:) and (else:) macros are convenient variants of (if:) designed to make this easier: you can merely say "if A happened", "else, if B happened", "else, if C happened" in your code.
Details:
Just like the (if:) macro, (else-if:) only checks its condition once, when the passage or hook contaning it is rendered.
The (else-if:) and (else:) macros do not need to only be paired with (if:)! You can use (else-if:) and (else:) in conjunction with boolean variables, like so:
(set:$married to false, $date to false)
$married[You hope this warrior will someday find the sort of love you know.]
(else-if: not $date)[You hope this warrior isn't doing anything this Sunday (because \
you've got overtime on Saturday.)]
If you attach (else-if:) to a named hook, and the (else-if:) hides the hook, you can reveal the hook later in the passage by using the (show:) macro to target the hook.
if you want to conditionally display very short strings, or small values inside a macro call, you may want to use the shorter (cond:) macro instead.
See also:
(if:), (unless:), (else:), (cond:), (show:)
The (else: ) macro
(else: ) → Changer
This is a convenient limited variant of the (else-if:) macro. It will simply show the attached hook if the preceding hook was hidden, and hide it otherwise. If there was no preceding hook before this, then an error message will be printed.
Example usage:
The coins fall...
\(if: (either:false, false, false, true))
[and both land on tails! That means you've won the bet!]
\(else: )
[and one of them lands heads-up.]
Rationale:
After you've written a series of hooks guarded by (if:) and (else-if:), you'll often have one final
branch to show, when none of the above have been shown. (else:) is the "none of the above" variant
of (else-if:), which needs no boolean expression to be provided. It's essentially the same as
(else-if: true)
, but shorter and more readable.
For more information, see the documentation of (else-if:).
Notes:
Just like the (if:) macro, (else:) only checks its condition once, when the passage or hook contaning it is rendered.
Due to a mysterious quirk, it's possible to use multiple (else:) macro calls in succession:
(set: $isUtterlyEvil to (either:true,false))
$isUtterlyEvil[You suddenly grip their ankles and spread your warm smile into a searing smirk.]
(else:)[In silence, you gently, reverently rub their soles.]
(else:)[Before they can react, you unleash a typhoon of tickles!]
(else:)[They sigh contentedly, filling your pious heart with joy.]
This usage can result in a somewhat puzzling passage source structure, where each (else:) hook alternates between visible and hidden depending on the first such hook. So, it is best avoided.
If you attach (else:) to a named hook, and the (else:) hides the hook, you can reveal the hook later in the passage by using the (show:) macro to target the hook.
See also:
(if:), (unless:), (else-if:), (cond:), (show:)
The (for: ) macro
(for: Lambda, [...Any]) → Changer
Also known as: (loop:)
When attached to a hook, this repeats the attached hook, setting a temporary variable to a different value on each repeat.
Example usage:
(for: each _item, ...$arr) [You have the _item.]
prints "You have the " and the item, for each item in $arr.(for: _ingredient where it contains "petal", ...$reagents) [Cook the _ingredient?]
prints "Cook the " and the string, for each string in $reagents which contains "petal".
Rationale:
Suppose you're using arrays to store strings representing inventory items, or character datamaps, or other kinds of sequential game information - or even just built-in arrays like (history:) - and you want to print out a sentence or paragraph for each item. The (for:) macro can be used to print something "for each" item in an array easily - simply write a hook using a temp variable where each item should be printed or used, then give (for:) an "each" lambda that uses the same temp variable.
Details:
If no extra values are given after the lambda (for instance, by using ...
with an empty array), then nothing will happen
and the attached hook will not be printed at all.
Don't make the mistake of believing you can alter an array by trying to (set:) the temp variable in each loop - such
as (for: each _a, ...$arr)[(set: _a to it + 1)]
. This will NOT change $arr - only the temp variable will change (and
only until the next loop, where another $arr value will be put into it). If you want to alter an array item-by-item, use
the (altered:) macro.
The temp variable inside the hook will shadow any other identically-named temp variables outside of it: if you
(set: _a to 1)
, then (for: each _a, 2,3)[ (print: _a) ]
, the inner hook will print "2" and "3", and you won't be
able to print or set the "outer" _a.
You may want to simply print several copies of a hook a certain number of times, without any particular
array data being looped over. You can use the (range:) macro with it instead: (for: each _i, ...(range:1,10))
, and
not use the temp variable inside the hook at all.
As it is a changer macro, (for:)'s value is a changer which can be stored in a variable - this command stores all of the values originally given to it, and won't reflect any changes to the values, or their container arrays, since then.
Alternatives:
You may be tempted to use (for:) not to print anything at all, but to find values inside arrays using (if:), or form a "total" using (set:). The lambda macros (find:) and (folded:), while slightly less straightforward, are recommended to be used instead.
See also:
The (either: ) macro
(either: ...Any) → Any
Give this macro several values, separated by commas, and it will pick and return one of them randomly.
Example usage:
A (either: "slimy", "goopy", "slippery") puddle
will randomly be "A slimy puddle", "A goopy puddle" or "A slippery puddle".(go-to: (either: "Void 2", "Void 3", "Void 4"))
will send the player to one of three random passages.(text-colour:(either: red, yellow, green))
will create a (text-colour:) changer using one of the three colours.
Rationale:
There are plenty of occasions where you might want random elements in your story: a few random adjectives or flavour text lines to give repeated play-throughs variety, for instance, or a few random links for a "maze" area. For these cases, you'll probably want to simply select from a few possibilities. The (either:) macro provides this functionality.
Details:
This is one of the features that uses Harlowe's pseudo-random number generator. If you use (seed:) at the start of the story, the chosen value will be predetermined based on the seed string, and how many other random macros and features have been used before it.
As with many macros, you can use the spread ...
operator to place all of the values in an array or dataset
into (either:), and pick them randomly. (either: ...$array)
, for instance, will choose one possibility from
all of the array contents.
If you want to pick two or more values randomly, you may want to use the (shuffled:) macro, and extract a subarray from its result.
If you want to pick a value more reliably - for instance, to pick a value randomly, but keep using that same value in subsequent visits to the passage - you may want to store an (either:) result in a variable using (set:) in an earlier passage, and use that whenever you want to use the result.
See also:
(nth:), (random:), (shuffled:), (cond:)
The (cond: ) macro
(cond: Boolean, Any, ...Any) → Any
When given a sequence of booleans (the "conditions") paired with values, this provides the first value that was
paired with a true
condition. This can give you one value or another based on a quick check.
Example usage:
(set: $status to (cond: $cash >= 300, "stable", $cash >= 200, "lean", $cash >= 100, "skint", "broke"))
Your (cond: $wonTheRace, "gasps of triumph", "wheezes of defeat") drown out all other noise.
Rationale:
While the (if:), (else:) and (else-if:) macros allow blocks of passage prose to be conditionally displayed and code to be conditionally run, there are plenty of situations where you'd prefer to succinctly select values inside macro calls, or select from multiple values, without needing to write multiple (else-if:)s or (set:)s for each possibility. The (cond:) macro (short for "condition") offers such utility.
In situations where you would write something like this,
{(set:$lostTheSword to (either:true,false))
(if: not $lostTheSword)[
(set: $weapon to "a holy sword")
](else: )[
(set:$weapon to "an unholy swear-word")
]}
you could instead simply write this.
(set:$lostTheSword to (either:true,false))(set: $weapon to (cond: not $lostTheSword, "a holy sword", "an unholy swear-word"))
Details:
This macro is intended to resemble the "cond" function in Lisp, as well as the "ternary" operator in numerous other programming languages (though it does not perform short-circuiting). It also might remind you of the values given to (dm:) - a piece of metadata, followed by its matching data - except that (dm:) ties names to data, whereas this ties conditions to data.
If only one value was given to (cond:), then that value will be returned as-is.
Except for the last, every odd value given to (cond:) must be a boolean, or an error will occur.
See also:
The (nth: ) macro
(nth: Number, ...Any) → Any
Given a positive whole number and a sequence of values, this selects the nth value in the sequence, where n is the number. If n is larger than the number of items in the sequence, the selection loops around to the start.
Example usage:
(nth: visit, "Hi!", "Hello again!", "Oh, it's you!", "Hey!")
will display a different salutation, in sequence, on the first, second, third and fourth visits, then return to "Hi!" on the fifth visit, and so on. This uses the "visits" identifier (also known as "visit").(nth: turn, "Full Moon", "Waning", "Halfmoon", "Crescent", "New Moon", "Crescent", "Halfmoon", "Waxing")
displays a different moon phase based on the current turn. This uses the "turns" identifier (also known as "turn"). This could be used in a "header" or "footer" tagged passage to display a moon phase in every passage.
Rationale:
This macro is designed to be used in passage prose, letting you quickly display one of a varying range of phrases or sentences based
on a certain value. In addition to being useful with story variables, it's useful with the visit
identifier, allowing you to
vary the text shown on each subsequent visit to a passage, with more consistent variation than if you were using (either:).
However, you can use (nth:) with any kind of value, not just strings. For instance, (text-colour: (nth: $wounds, white, yellow, red))
will produce a (text-colour:) changer that differs in colour based on the number in $wounds (up to 3).
Details:
You can, of course, access a specific value in a sequence using the (a:) macro and the 's
or of
syntax - (a: 1,2,3)'s ($n)
is functionally very similar to (nth: $n, 1, 2, 3)
, and other uses of the (nth:) macro. (nth:), however, allows the given value to
exceed the bounds of the sequence - (nth: 4, 1, 2, 3)
would produce 1, whereas (a: 1,2,3)'s 4th
would produce an error.
If you wish to use (nth:) to display very large blocks of prose, you may wish to simply put that prose in hooks, and use (if:) to selectively display
one, such as by writing (if: visits is 3)
.
If you don't want the "looping" to occur - if you want to only return the final value if the number exceeds the sequence - you can combine
this macro with (min:). (nth: (min: 3, visit), "", "", "")
You may be tempted to combine this macro with (shuffled:), as in (nth: visit, ...(shuffled: "A", "B", "C"))
- however, this will NOT
behave any differently from just using (either:) - each visit, the (shuffled:) macro will shuffle the sequence in a different way, so you
can't guarantee that different values will be shown.
See also:
The (verbatim: ) macro
(verbatim: ) → Changer
Also known as: (v6m:)
When attached to a hook or command, the markup inside that would normally be rendered into HTML is instead presented as plain text, as if the verbatim markup was used on it.
Example usage:
(v6m: )[ \(`A`)/ ]
prints a kaomoji without fear of its source being interpreted as markup.
Rationale:
Harlowe conveniently allows you to print strings containing markup and variables, such as "Your rank is ''$rank''"
, rendering
them as if they were written directly in the passage. However, there are many situations where you would prefer not to do so,
and where you can't conveniently wrap that content in the verbatim markup. Chief among these is player-inputted text: since
players can write valid Harlowe markup into (prompt:) and (input-box:) elements, displaying such text could cause no end of
disaster for your story. Additionally, since this text can also include unmatched verbatim markup, attempting to encase it
in verbatim markup is non-trivially difficult. This macro provides an easier way to guarantee that the markup, if present, is not
rendered.
In addition, you may want to write a hook without having to worry about the task of placing its contents inside verbatim markup, or write a hook containing textual references to HTML or Harlowe code. Even if it turns out to be unnecessary, having this macro on hand can be reassuring.
Details:
This macro takes no values - each changer value it produces is the same.
If you would like to use this macro to simply print a variable's contents, the (verbatim-print:) macro may be more to your liking.
See also:
(collapse:), (verbatim-print:) (verbatim-source:)
The (verbatim-print: ) macro
(verbatim-print: Any) → Command
Also known as: (v6m-print:)
A convenient combination of (verbatim:) and (print:), this prints out any single argument given to it, as text, but without rendering the resulting text as markup.
Example usage:
(v6m-print: "<sarcasm>" + $quip + "</sarcasm>")
prints out the string"<sarcasm>"
, the string contents of$quip
, and the string"</sarcasm>"
, without interpreting that as HTML markup.(set: $name to (v6m-print: (prompt: "Enter your name:", "")))
prompts the player for their name, then stores a command that displays that name verbatim whenever it's printed.
Rationale:
In practice, this is functionally identical to a (verbatim:) changer attached to a (print:) command. However, one major difference is that this can be stored in a variable and used in passage prose by itself, without having to attach the changer each time. This scenario is especially useful when dealing with player-inputted text: rather than having to display it with two macros each time, you can simply save this command in a variable and use that variable.
Details:
As with (print:), once text is given to this command, there's no easy way to extract it from the command value
without using (source:). So, you can't provide it to (upperfirst:) and other such macros.
(upperfirst: (verbatim-print: $name))
will produce an error. Instead, convert the original string
using (upperfirst:) before giving it to (verbatim-print:).
If you have a string you need to print frequently, and you don't want to call (verbatim-print:) every time you need to print it,
you may wish to simply (set:) a (verbatim-print:) into a variable, like so: (set: $vbName to (verbatim-print:$name))
. Then, you can
put the command (set in that variable) into passage prose, and it will work as expected.
See also:
The (change: ) macro
(change: HookName or String, Changer or Lambda) → Command
Applies a changer (or a "via" lambda producing a changer) to every occurrence of a hook or string in a passage, once.
Example usage:
(change: "gold", (text-colour: yellow) + (text-style:'bold'))
makes all prior occurrences of "gold" in the text be bold and yellow.(change: ?passage's chars, via (text-color:(hsl: pos * 10, 1, 0.5)))
colours all of the characters in the passage in a rainbow pattern.
Rationale:
While changers allow you to style or transform certain hooks in a passage, it can be tedious and error-prone to attach them to every occurrence as you're writing your story, especially if the attached changers are complicated. You can simplify this by storing changers in short variables, and attaching just the variables, like so:
(set: _ghost to (text-style:'outline'))
_ghost[Awoo]
_ghost[Ooooh]
Nevertheless, this can prove undesirable: you may want to not use the _ghost styling later in development, which would force you to remove the attached variables to avoid producing an error; you may want to only style a single word or phrase, and find it inconvenient to place it in a hook; you may simply not like dedicating variables to storing changers, or in placing (set:) macros at the start of your passage's prose.
Instead, you can give the hooks the name "ghost", and then (change:) them afterward like so:
|ghost>[Awoo]
|ghost>[Ooooh]
(change: ?ghost, (text-style:'outline'))
This has a few advantages. As it ties the changer styling to a hook name rather than a variable, the (change:) can be removed later without causing errors. Placing the (change:) at the end of the passage can also make the passage's source more readable, the textual content being closer to the top.
Details:
The (change:) macro can target plain text instead of hooks, much like (click:) - simply provide a string instead of a hook name.
If a "via" lambda is supplied to (change:) instead of a changer, then that lambda is used to compute a changer dynamically, using the pos
keyword
to distinguish each hook that's enchanted. For instance, (change: "O", via (text-style:(cond: pos is an even, 'bold', 'none')))
changes only
even-numbered instances of the letter "O".
Like the (replace:), (append:) and (prepend:) macros, this macro does not affect text and hooks that appear after it, as it is an immediate command that only affects what has already been rendered. For an alternative version of this macro which does affect hooks and text after it, see (enchant:).
The built-in hook names, ?Page, ?Passage, ?Sidebar and ?Link, as well as their data names like chars
or lines
, can be targeted by this macro,
and can be styled on a per-passage basis this way.
Using (text-colour:) with this macro will let you change the colour of links inside the indicated hook, with one exception:
using (change:) to change the entire passage (via ?passage
or ?page
) with (text-colour:) will NOT affect links. This is to allow you
to re-style the entire story without having to lose the distinct colour of links compared to passage text. You can change the colour of all links using
an explicit (change: ?link, (text-colour: $color))
or by using (link-style: (text-colour: $color))[=
(that is, with unclosed hook markup).
You can't use this macro to change the appearance or behaviour of a completely empty hook, such as |A>[]
. Completely empty hooks (that haven't had text inserted
by (replace-with:) and the like) are always hidden by Harlowe.
You can use (change:) with (transition:) to add transitions to hooks or text elsewhere in the same passage – however, if the (change:) macro is
run after the passage was initially rendered, the transitions will begin animating in the middle of their usual animations, or, if enough time
has passed, won't run at all. For example, (event: when time > 2s)[(change:"Riddles", (t8n:"Shudder")+(t8n-time:3s))]
will
apply a 3-second transition to each instance of the word "Riddles" in the passage, but since 2 seconds have already passed since those words were
rendered, only the last 1 second of the transition will be visible.
You cannot use (change:) with (link:), (replace:), or any of its relatives – because the enchanted hook or text is already in the passage, the link can't appear and it can't replace anything.
See also:
(enchant:), (enchant-in:), (replace:)
The (enchant: ) macro
(enchant: HookName or String, Changer or Lambda) → Command
Applies a changer (or a "via" lambda producing a changer) to every occurrence of a hook or string in a passage, and continues applying that changer to any further occurrences that are made to appear in the same passage later.
Example usage:
(enchant: "gold", (text-colour: yellow) + (text-style:'bold'))
makes all occurrences of "gold" in the text be bold and yellow.(enchant: ?passage's chars, via (t8n-delay:pos * 30) + (t8n:'instant'))
causes the passage's characters to "type out" when the player first visits, such as in a visual novel or other such computer game. Unlike (change:), this works well with instances of(click:?page)[==
later in the passage.
Rationale:
This is a special version of (change:) which doesn't just perform a single transformation of a set of hooks or text - rather, like (click:), it creates an ongoing effect that constantly checks and reapplies the changers whenever new hooks or text are inserted into the passage, persisting until you navigate to another passage. Consider the following:
(enchant: ?ghost, (text-style:'outline'))
|ghost>[Awooo]
(link:">Wait.")[|ghost>[Oooowooo]]
If this were a (change:) command, the second hook revealed by the (link:) wouldn't be affected, as it is inserted into the passage after it's finished rendering. The (enchant:) macro allows you to guarantee that every hook or bit of text that you want the changer to affect is constantly affected.
Details:
The (enchant:) macro takes the same values as (change:) - a string can be given instead of a hook name, and a lambda can be given instead of a changer. See (change:)'s article for more details about these.
This macro works well in "header" or "footer" tagged passages - using a lot of (enchant:) commands to style certain words or parts of every passage, you can essentially write a "styling language" for your story, where certain hook names "mean" certain colours or behaviour. (This is loosely comparable to using CSS to style HTML class names, but exclusively uses macros.)
When targeting ?Page, ?Passage and ?Sidebar, there is generally no difference between using (enchant:) and using (change:), as there (usually) aren't any other hooks with those names in the passage.
Like (change:), you cannot use (enchant:) with (link:), (replace:), or any of its relatives.
The enchantment created by this macro cannot change the appearance or behaviour of a completely empty hook, such as |A>[]
.
However, once a hook stops being empty (such as when the (append:) macro appends to it), the
enchantment created by this macro will automatically start applying to it.
See also:
(click:), (change:), (enchant-in:)
The (enchant-in: ) macro
(enchant-in: HookName or String, Changer or Lambda) → Changer
A variation of (enchant:) and (change:), this applies a changer to every occurrence of a hook or string within just the attached hook, rather than the whole passage. As with (enchant:), the changer will be applied to every additional occurrence inserted into the attached hook.
Example usage:
(enchant:?frog, (text-style:"italic"))
"Opening remarks?"
|frog>["Crok, crok, crok."]
(enchant-in: ?frog, (text-colour:green))
["Your response?"
|frog>["Croak, croak."]
"A stunning rebuke!"
|frog>["Croooak."]]
Rationale:
While (change:) and (enchant:) both allow hooks to have changers or styles applied to them, these macros produce commands that must be placed in the passage, and which
affect every match within the passage. It can sometimes be convenient to restrict the effect of (enchant:) to just matches within a single area of prose, especially when matching
using strings, the ?Link
hook name, or ?Page's chars
. Thus, you can use (enchant-in:), attaching it to a hook that encloses the area you want it to affect. The enchantment it
produces will be treated as though it didn't exist outside of the attached hook.
Details:
You can use built-in hook data names such as lines
and chars
with this macro, such as by (enchant-in: ?page's lines, $changer)
, which will style all of the lines in the attached
hook with $changer. However, this construction appears counterintuitive when written out - the HookName selects all of the lines in the page, but only those within the attached hook are
styled. So, more readable shorthand macros exist for both of these - (line-style:) and (char-style:) - which you ought to use instead.
This macro takes the same values as (enchant:) and (change:), and will produce the same errors for the same values. So, (link:), (replace:), or any of its relatives cannot be given as the second value, and neither can a lambda that doesn't produce a changer.
Note that this macro can only affect explicit hooks or string occurrences, and can't affect just "part" of a target. For instance, (enchant-in: ?page, (background:red))[DANGER]
will NOT turn
the background of the attached hook red, but (enchant-in: ?page's lines, (background:red))[DANGER]
will (because the text "DANGER" is a line of text, and is thus targeted by ?page's lines
).
This enchantment will be listed in the "Enchantments" tab of the Debug Mode panel when it's active, alongside enchantments created by (enchant:).
Due to Harlowe engine limitations, this currently does NOT work when created by a lambda given to (enchant:)
or (change:)
, such as in (enchant: ?passage, via (enchant-in:?frogs,(bg:(hsl:pos*30,0.5,1))))
.
See also:
(enchant:), (change:), (link-style:)
The (hooks-named: ) macro
(hooks-named: String) → HookName
When given a string, this creates a HookName from it. This can be used to dynamically create HookNames.
Example usage:
|oracle)["I scry with sticks, not bones."]|mage)["No teeth in the jawbones?"]|bodyguard)["Don't sift through rot."]
(set: $companionType to "bodyguard")
(link:"Investigate the bones")[(show:(hooks-named:$companionType))]
Rationale:
The standard syntax for referring to hooks, in macros such as (replace:), (change:) or (show:), is to write a HookName, such as ?door
. That syntax,
though, requires that you hard-code the name of the hook. This macro lets you construct a HookName from one or more existing strings or other variables,
so that the exact hook referenced depends on the game state.
This macro is called (hooks-named:) to avoid confusion with (hook:), and also to convey that a HookName will refer to any number of hooks as long as they have the same name.
Details:
Note that the HookNames produced by this macro have the same functionality as other HookNames. In particular, you can specify the 1st
hook, 2ndlast
and so forth
by writing, for instance, (hooks-named: "A")'s 2ndlast
. Also note that the built-in HookNames can be constructed with this macro - (hooks-named:"passage")
is
the same as ?passage
.
If an empty string is given, then this will cause an error.
See also:
The (border: ) macro
(border: String, [String], [String], [String]) → Changer
Also known as: (b4r:)
A changer macro that applies a CSS border to the hook.
Example usage:
(b4r:"dotted")[I love you!
I want to be your wife!]
Details:
The border macros accept up to four values. These values refer to sides of a rectangle, going clockwise from the top: the first value is the top edge (12 o'clock), second is the right edge (3 o'clock), third is the bottom edge (6 o'clock), fourth is the left edge (9 o'clock). You can stop giving values anywhere. If an edge doesn't have a value, then it will use whatever the opposite edge's value is.
(border: "solid", "dotted", "dashed", "double")
provides all four sides.(border: "solid", "dotted", "dashed")
stops at the bottom edge, so the left edge will use "dotted", to match the right edge.(border: "solid", "dotted")
stops at the right edge, so the bottom edge will use "solid", to match the top edge, and the left edge will use "dotted", to match the right edge.(border: "solid")
causes all of the edges to use "solid".
This macro affects the style of the border, and accepts the following border names.
String | Example |
---|---|
"none" | Example text |
"solid" | Example text |
"dotted" | Example text |
"dashed" | Example text |
"double" | Example text |
"groove" | Example text |
"ridge" | Example text |
"inset" | Example text |
"outset" | Example text |
The "none" type can be used to remove a border that another changer may have included. NOTE: As of Harlowe 3.2.2,
this can only be used to remove borders from combined changers, such as by (set: $changer to it + (b4r:"none"))
,
and can't be used to remove borders from already-changed hooks or other structures.
The default size of the border, with no other CSS changes to any elements, is 2px (2 pixels), unless a change is applied using (border-size:).
Due to browser CSS limitations, the border will force the hook to become a single rectangular area. The hook can no longer word-wrap, and moreover occupies every line in which its text is contained. So, this changer is best suited for entire paragraphs of text (or hooks using the (box:) changer) rather than single words or phrases.
See also:
(border-size:), (border-colour:), (corner-radius:)
The (border-colour: ) macro
(border-colour: String or Colour, [String or Colour], [String or Colour], [String or Colour]) → Changer
Also known as: (b4r-colour:), (border-color:), (b4r-color:)
When applied to a hook being changed by the (border:) changer, this changes the border's colour.
Example usage:
(b4r-color:magenta)+(b4r:"ridge")[LEVEL 01: DREAM WORLD]
(b4r-color:red,yellow,green,blue)+(b4r:"dotted")[Isn't it a lovely time?]
Details:
The border macros accept up to four values. These values refer to sides of a rectangle, going clockwise from the top: the first value is the top edge (12 o'clock), second is the right edge (3 o'clock), third is the bottom edge (6 o'clock), fourth is the left edge (9 o'clock). You can stop giving values anywhere. If an edge doesn't have a value, then it will use whatever the opposite edge's value is (or the top value if it's the only one).
Much like (text-colour:), this accepts either a Colour (such as those produced by (hsl:) or (rgb:), or plain literals
like #fff
), or a CSS colour string.
Certain (border:) styles, namely "ridge", "groove", "inset" and "outset", will modify the colour, darkening it for certain parts of the border to produce their namesake appearance.
Selecting "transparent"
as the colour will cause the border to "disappear", but also cause the space surrounding
the hook to remain.
See also:
The (border-size: ) macro
(border-size: Number, [Number], [Number], [Number]) → Changer
Also known as: (b4r-size:)
When applied to a hook being changed by the (border:) changer, this multiplies the size of the border by a given amount.
Example usage:
(b4r:"solid")+(b4r-size:4)[Do not read anything outside of this box.]
Details:
The border macros accept up to four values. These values refer to sides of a rectangle, going clockwise from the top: the first value is the top edge (12 o'clock), second is the right edge (3 o'clock), third is the bottom edge (6 o'clock), fourth is the left edge (9 o'clock). You can stop giving values anywhere. If an edge doesn't have a value, then it will use whatever the opposite edge's value is (or the top value if it's the only one).
The default size of borders added using (border:) is 2px (2 pixels). The number given is a number of CSS pixels to set the new size to. Since CSS pixels don't exactly correspond to display pixels (such as, for instance, if the browser window is zoomed in) then it's possible to have a non-whole number of CSS pixels (such as 1.5, which would, if the browser window was zoomed in to 200%, become 3 display pixels). Thus, this macro accepts numbers with fractional values. That being said, if a number lower than 0 is given, an error will be produced.
See also:
(border:), (corner-radius:), (text-size:)
The (corner-radius: ) macro
(corner-radius: Number, [Number], [Number], [Number]) → Changer
When applied to a hook, this rounds the corners by the given number of pixels, causing the hook to become increasingly round or button-like.
Example usage:
(b4r:'solid')+(corner-radius:8)[Hasn't this gone on too long?]
(b4r:'solid')+(corner-radius:12)[Shouldn't you tell them the truth?]
(b4r:'solid')+(corner-radius:16)[//That you're not really who you say you are??//]
Details:
The border macros accept up to four values. These values refer to corners of a rectangle, going clockwise from the top: the first value is the top-left corner (10 o'clock), second is the top-right corner (2 o'clock), third is the bottom-right corner (4 o'clock), fourth is the bottom-left corner (8 o'clock). You can stop giving values anywhere. If a corner doesn't have a value, then it will use whatever the opposite corner's value is (or the top-left value if it's the only one).
Obviously, unless the hook has a (bg:) or a (border:), the rounded corners will not be visible, and this changer will have no real effect.
If the hook has a (border:), values greater than the border's (border-width:) (which is 2 if it wasn't changed) will cause the interior of the element to become constrained by the curvature of the corners, as the rectangle's corners get cut off. Because of this, this macro also adds interior padding (distance between the border and the contained text) equal to each of the passed-in numbers, unless another changer (such as (css:)) provided a different padding value.
See also:
(border:), (bg:), (border-size:)
The (hsl: ) macro
(hsl: Number, Number, Number, [Number]) → Colour
Also known as: (hsla:)
This macro creates a colour using the given hue (h) angle in degrees, as well as the given saturation (s) and lightness (l) percentages, and, optionally, the transparency (alpha, or a) percentage, which is a fractional value between 0 (fully transparent) and 1 (fully visible).
Anything drawn with a partially transparent colour will itself be partially transparent. You can then layer such elements to produce a few interesting visual effects.
Example usage:
(hsl: 120, 0.8, 0.5)
produces. (hsl: 28, 1, 0.4)'s h
produces the number 28.(hsl: 120, 0.5, 0.8, 0.6)
produces(a 40% transparent green).
Rationale:
The HSL colour model is regarded as easier to work with than the RGB model used for HTML hexadecimal notation and the (rgb:) macro. Being able to set the hue with one number instead of three, for instance, lets you control the hue using a single variable, and alter it at will.
Details:
This macro takes the same range of numbers as the CSS hsla()
function.
Giving saturation or lightness values higher than 1 or lower than 0 will cause an error. However,
you can give any kind of hue number to (hsl:), and it will automatically round it to fit the 0-359
degree range - so, a value of 380 will become 20. This allows you to cycle through hues easily by
providing a steadily increasing variable or a counter, such as (hsl: time / 100, 1, 0.5)
.
Giving alpha percentages higher than 1 or lower than 0 will cause an error.
See also:
The (rgb: ) macro
(rgb: Number, Number, Number, [Number]) → Colour
Also known as: (rgba:)
This macro creates a colour using the three red (r), green (g) and blue (b) values provided, whose values are numbers between 0 and 255, and, optionally, the transparency (alpha, or a) percentage, which is a fractional value between 0 (fully transparent) and 1 (fully visible).
Anything drawn with a partially transparent colour will itself be partially transparent. You can then layer such elements to produce a few interesting visual effects.
Example usage:
(rgb: 255, 0, 47)
produces. (rgb: 90, 0, 0)'s r
produces the number 90.(rgb: 178, 229, 178, 0.6)
produces(a 40% transparent green).
Rationale:
The RGB additive colour model is commonly used for defining colours: the HTML hexadecimal notation for colours (such as #9263AA) simply consists of three hexadecimal values placed together. This macro allows you to create such colours computationally, by providing variables for certain components.
Details:
This macro takes the same range of numbers as the CSS rgb()
function.
Giving values higher than 255 or lower than 0 will cause an error. Former versions of Harlowe did not allow fractional values to be given, but that restriction is no longer present.
Giving alpha percentages higher than 1 or lower than 0 will cause an error.
See also:
The (lch: ) macro
(lch: Number, Number, Number, [Number]) → Colour
Also known as: (lcha:)
This macro creates a colour using three values in the CIELAB colour model - a lightness (l) percentage, a chroma (c) value, and a hue (h) angle in degrees, and, optionally, the transparency (alpha, or a) percentage, which is a fractional value between 0 (fully transparent) and 1 (fully visible).
Anything drawn with a partially transparent colour will itself be partially transparent. You can then layer such elements to produce a few interesting visual effects.
Example usage:
(lch: 0.6, 80, 10)
produces. (lch: 0.6, 80, 10)'s lch's c
produces the number 80.(lch: 0.9, 15, 142, 0.6)
produces(a 40% transparent green).
Rationale:
The CIELAB colour model is considered to be more universally useful than the RGB model and its HSL representation,
whose treatment of "lightness" doesn't properly reflect the actual perceived luminosity of the colours
in question. For instance, this colour (hsl:120,1,0.5)
) and this colour (hsl:220,1,0.5)
) have the same HSL lightness (0.5), but one appears to the human eye to be less
bright than the other, due to one hue being less luminous than the other.
The lightness in LCH more closely corresponds to how the human eye perceives luminosity - (lch:0.9,80,120)
produces (lch:0.9,80,220)
produces
Additionally, CIELAB's colour model replaces the "saturation" value of HSL with "chroma". Rather than being a single percentage from 0 to 1, LCH's chroma is a value whose upper bound varies with the colour's hue, reflecting how the human eye distinguishes some hues more accurately than others.
Details:
Despite all of the above, any colour produced by this macro will have to be internally converted back to HSL in order to be used, due to HTML and CSS not fully supporting LCH as of 2020. As such, colours produced by this macro are constrained by HSL's limits - as LCH accepts a greater variety of chroma and lightness combinations than what HSL can represent, the output colour will be automatically converted to the nearest valid HSL values, if necessary.
Giving lightness or alpha values less than 0 and greater than 1 will cause an error. Giving chroma values less than 0
and greater than 132 will cause an error. However, you can give any kind of hue number to (lch:), and it will automatically
round it to fit the 0-359 degree range - so, a value of 380 will become 20. This allows you to cycle through hues easily by
providing a steadily increasing variable or a counter, such as (lch: 0.9, 80, time / 100)
.
See also:
(hsl:), (rgb:), (gradient:), (complement:), (mix:)
The (complement: ) macro
(complement: Colour) → Colour
When given a colour, this provides a complement to that colour.
Example usage:
(complement:orange)
produces
Details:
This is a very simple macro - the returned colour is the same as the input colour, except that its LCH hue (as given to the (lch:) macro) has been rotated by 180 degrees, producing a colour with equivalent chroma and luminosity, but an opposite hue.
Note that, unlike (text-colour:), this will not take a string containing a CSS colour. This is because it operates purely on Harlowe colour data, and doesn't have a means of converting CSS colours into colour data.
See also:
The (palette: ) macro
(palette: String, Colour) → Array
When given a string specifying a palette type, and a colour, this macro produces an array containing the given colour followed by three additional colours that together form a palette, for use with (text-colour:), (bg:), and other macros.
Example usage:
{(unpack: (palette: "mono", orange + black) into (a:_bg, _c, _lc, _hc))
(enchant: ?page, (background: _bg) + (text-colour:_c))
(enchant: ?link, (text-colour:_lc) + (hover-style: (text-colour:_hc)))}
This passage uses (link:"(more)")[a brown palette.]
Rationale:
Intended for game jams and rapid prototyping, (palette:) provides a quick and simple palette for stories and passages. When you aren't too fussed with making your story look significantly different from the Harlowe default, but just want a certain colour scheme to provide a certain mood, or colour a specific passage differently to offset it from the rest of the story, you can defer the task of choosing text or background colours to this macro. It will choose colours which contrast with the given colour to attempt to maximise readability, while still having an interesting relationship with the given colour's hue.
Details:
The following type strings are accepted.
String | Explanation |
---|---|
"mono" | The returned colours are tints and shades of the given colour. |
"adjacent" | The returned colours' hues are 30° to the left, 30° to the right, and 60° to the right of the given colour's hue. |
"triad" | The returned colours' hues are 140° to the left, 140° to the right, and 180° to the right of the given colour's hue. |
This macro interprets the passed-in colour as a background colour, and the three colours it provides are intended as text colours - but you can easily use them for other purposes. The given colour could be used as a text colour, and any of the received colours could be used as different backgrounds.
The three returned colours all have a luminosity chosen to provide sufficient contrast with the given colour's luminosity. If the given colour's luminosity is very low or very high (near 0 or 1) then the returned colours will have a luminosity near the other extremity.
The (gradient: ) macro
(gradient: Number, ...Number, Colour) → Gradient
When given a degree angle, followed by any number of number-colour pairs called "colour stops", this macro produces a gradient that fades between those colours in the direction of the angle.
Example usage:
(set: $desertChrome to (gradient: 0, 0, #e6a860, 0.49, black, 0.5, white, 1, blue))
(background: $desertChrome)+(text-color:white)[Sunshine Desert]
The above example produces Sunshine Desert
Rationale:
An easy way to add a subtle sense of depth, texture, direction or variety to elements in Harlowe, without having to create and import background images from outside of Twine, is to use this macro to generate a gradient, a dynamically-generated background which can be used with (background:).
A gradient consists of a series of flat colours. One of those colours will be used on one side of the element, one on the other, and the space in between will smoothly fade between them. You can supply additional colours that the gradient will smoothly fade to in between the start and end colours, too.
To specify where exactly these intermediate colour fades will occur on the element, the colours are paired with a percentage number - 0 being one side of the element, 1 being the other, 0.5 being exactly in-between. This pairing is called a "colour stop".
Consider this (gradient:) call, with six colour stops.
(gradient:90, 0,#bf3f3f, 0.2,#a5bf3f, 0.4,#3fbf72, 0.6,#3f72bf, 0.8,#a53fbf, 1,#bf3f3f)
The six colour stops are 0,#bf3f3f
,
0.2,#a5bf3f
,
0.4,#3fbf72
,
0.6,#3f72bf
,
0.8,#a53fbf
,
and 1,#bf3f3f
.
This corresponds to the following gradient, which for documentation purposes has its colour stops marked.
#bf3f3f
#a5bf3f
#3fbf72
#3f72bf
#a53fbf
#bf3f3f
Any angle can be given to (gradient:), including diagonal values like 40 or 66.
Details:
An error will be produced if you give colour-stop percentages that aren't between 0 and 1, or give less than 2 colour-stops. However, any number of degrees given to (gradient:), even below 0 or above 359, will automatically be rounded to fit the 0-359 degree range - so, a value of 380 will become 20.
You do not necessarily need to supply colour-stops at positions 0 and 1 - instead, the nearest colour-stop to those positions will be extended to the edge of the gradient. Furthermore, you don't need to supply colour-stops in ascending order - they will be reordered by Harlowe if they are not.
Gradients in Harlowe are implemented using CSS linear-gradient
s, and have the same limitations in output and browser support.
Note, however, that the order of values for a colour stop is reversed from that of the CSS syntax (numbers go first, then colours).
This is to help ensure that the initial degree number is not confused for a colour-stop percentage. Additionally, CSS
linear-gradient "colour hints", which are used to adjust the midpoints between colour stops, are currently not supported by this macro.
See also:
The (stripes: ) macro
(stripes: Number, Number, Colour, Colour) → Gradient
When given a degree angle, a pixel distance, and two or more colours, this macro produces a gradient that draws a striped background, with each stripe as wide as the distance, and alternating through the given colours.
Example usage:
(enchant: ?page, (background: (stripes: 45, 20, fuchsia+white, white)))
causes the page to have a pink-and-white striped background.
Rationale:
The (gradient:) macro can be used to dynamically create gradient backgrounds, which smoothly transition between multiple colours. By using certain pairs of colour stops that are very close together, however, you can create gradients where the colours transition sharply, producing stripes. Rather than use that macro, you can instead use this one to generate striped backgrounds succinctly, that repeat uniformly, and with easily-adjusted stripe width.
Details:
The degree angle matches that given to (gradient:). A number of 0 causes the stripes to be drawn horizontally, and increasing that number rotates the stripes counter-clockwise. Any number below 0 or above 359 will automatically be rounded to fit the 0-359 degree range - so, a value of 380 will become 20.
The distance value given is in pixels, and determines the width of a single stripe.
Gradients (including those produced by (stripes:)) in Harlowe are implemented using CSS repeating-linear-gradient
s, and have the same limitations
in output and browser support.
(stripes:) gradients still have a "stops" array, accessible via $stripeGradient's stops
, as with other gradients. Even though (stripes:) doesn't accept
"colour stops", this array still contains colour stop datamaps, as if this gradient had been generated by (gradient:).
There are two "stops" for each colour, and instead of a "percent" value, they have a "pixels" value. So, $stripeGradient's stops's 1st's colour
will
produce the colour of the first stripe, $stripeGradient's stops's 3rd's colour
will produce the colour of the second stripe, and so forth.
$stripeGradient's stops's 2nd's pixels
will produce the pixel width of each stripe.
See also:
The (mix: ) macro
(mix: Number, Colour, Number, Colour) → Colour
When given two pairs of values - each a number from 0 to 1 and a colour - this macro produces a mix of the two colours, using the numbers as ratios of each colour. The colours are mixed using the LCH colourspace, used by the (lch:) macro.
Example usage:
(mix: 0.5, red, 0.5, blue)
produces. This is a rosy fuchsia, whereas red + blue
produces, a grayer fuchsia. (mix: 0.5, white, 0.5, #00f)
produces. The slight shift towards purple is a known weakness of the LCH colourspace. (mix: 0.4, red, 0.1, white)
produces, a 50% transparent light red. Because the numbers only added up to 0.5, the result was 50% transparent (see below).
Rationale:
While you can mix colours in Harlowe using the + operator, such as by red + yellow
, this operation doesn't easily
allow for different mix ratios, such as a one-quarter/three-quarters mix, instead mixing both colours equally.
In addition, in Harlowe 3, this uses the sRGB mixing method, which doesn't produce the most perceptually ideal colours, often making them
darker or grayer than expected. This macro provides a more sophisticated alternative, allowing ratios for each colour to be specified, and using the LCH colour
space to mix the colours, which tends to preserve chromaticity (colourfulness) better.
Details:
The colours are mixed in the LCH colourspace. What this means is: the colours are converted to their LCH datamaps (accessible as $colour's lch
), and then
(omitting a few minor details) their hue, lightness and chroma values are averaged. Then, the averaged values are used to construct a new colour, as if by the
(lch:) macro.
The two numbers (the ratios) are decimal values from 0 to 1. Numbers above or below will produce an error when given.
If the two ratios do not add up to 1, then they will be scaled to compensate. For instance, for (lch: 0.1, green, 0.3, blue)
, the 0.1 and 0.3 add up to 0.4,
but they should add up to 1, so they are scaled up to 0.25 and 0.75, respectively.
Additionally, if the two ratios add up to less than 1, then the difference will be converted into additional transparency, which is used to multiply
the mixed colour's alpha. For instance, for (lch: 0.15, red, 0.45, blue)
, the ratios 0.15 and 0.45 add up to only 0.6, which is 0.4 less than 1.
So, the mixed colour's alpha (of 1) is multiplied by 0.6 (and thus made 40% more transparent than it would've been otherwise).
See also:
The (macro: ) macro
(macro: [...TypedVar], CodeHook) → CustomMacro
Use this macro to construct your own custom macros, which you can (set:) into variables and call as easily as a built-in macro.
Example usage:
The following custom macro creates a command that displays a randomly rotated hook in red, with the given opacity.
(set:$ghostlyLaughter to (macro: num-type _o, [
(output: )+(text-rotate:(random:0,360))+(text-colour:(hsla:0, 1, 0.5, _o))[HE HE HE]
]))
($ghostlyLaughter:0.9) ($ghostlyLaughter:0.5) ($ghostlyLaughter:0.3)
The following custom macro creates a text string based on how many turns the player has taken. It takes no data.
(set: $fancyTimeName to (macro: [
(set: _timeOfDay to turns % 24 + 1)
(output-data: (a:
"midnight", "dreamshour", "wolfshour", "dark's end", "lightbreak", "afterdawn", "early rise", "awakening",
"early warming", "joyshour", "first lunch", "shadow's end", "zenith", "shadow's birth", "second lunch", "hopeshour", "early cooling",
"lightfade", "sundown", "dark's birth", "supper", "early rest", "slumbering", "catshour"
)'s (_timeOfDay))
]))
It is now ($fancyTimeName:).
The following custom macro takes a datamap containing a character's attributes, and prints a line of text describing a character.
(set: $charSheet to (dm: "name", str, "HP", num, "poison", num, "heartbreak", bool))
(set: $healthSummary to (macro: $charSheet-type _stats, [
This text inside the macro is not displayed during the game.
(set: _TheyAre to _stats's name + " is ")
Dead characters get a single, pithy line.
(if: _stats's HP <= 0)[(output: _TheyAre + "deceased.")]
Living characters get specific status conditions referred to.
(output-data:
_TheyAre + "in " + (cond: _stats's HP > 50, "fair", "poor") + " health." +
(cond: _stats's poison > 0, " " + _TheyAre + "poisoned.", "") +
(cond: _stats's heartbreak, " " + _TheyAre + "heartbroken.", "")
)
]))
(set: $steelyStats to (dm: "name", "Steely", "HP", 80, "poison", 0, "heartbreak", true))
($healthSummary: $steelyStats)
Rationale:
This macro provides you with the means to expand Harlowe's collection of built-in macros with custom utilities tailored specifically for your story. While many Twine projects are simple hypertext stories, there are many that use it to make more complicated simulations, role-playing games, generative art, and so on. Being able to craft a personal language of macros in which to write the many algorithms and textual structures such games involve is essential to keeping your code succinct and readable.
Writing the parameters:
Custom macros consist of two structures: a set of data inputs (called parameters), and a body of code that creates the output.
Each parameter consists of a datatype or pattern of data, the "-type" suffix, and a temp variable, just like typed temp variables created with (set:).
When you, the author, call the macro and give data at that parameter's position, it is put into the temp variable if it fits the datatype.
A macro stored in $treasure with str-type _name, num-type price
can be called by ($treasure: "Gold Watch", 155)
.
The types are checked, and if they don't match (for instance, by incorrectly writing ($treasure: 155, "Gold Watch")
),
then an error will result. This ensures that incorrectly written custom macro calls are caught early, just like with built-in macros.
As with TypedVars used in other places, you can use a complex data structure as the "type" of the variable - (a: num, num)-type _coords
specifies a parameter that requires an array of two numbers. If you wish to write a very general value-selection or data-structure macro,
such as (a:)
or (either:)
, that can take any kind of data value, you can write any-type
for that parameter. However, using any-type
is
not recommended unless you genuinely need it, as you miss out on the ability to catch wrong-type errors.
You might, on occasion, want to make a macro that can take an arbitrary amount of values, similar to certain built-in macros like (a:)
,
(altered:)
, and so forth. To do this, you can place the spread ...
syntax in front of a parameter's datatype. Just as a spread datatype
matches zero or more values when it is used with the matches
operator, a spread parameter represents zero or more values of the same data type
that you give to a macro call. Think of this as the opposite counterpart of the spread ...
syntax in macro calls. Instead of turning one value
(such as an array) into many spread-out values, this turns many values into a single array value. A custom macro stored in $mean with ...num-type _n
can be called
with ($mean:1,4,5,6)
, which sets _n to (a:1,4,5,6)
. ($mean:2,3)
sets _n to (a:2,3)
, and ($mean:)
sets _n to (a:)
. Note that
because it takes every value at or after it, it must be the final parameter of your custom macro.
(set: $mean to (macro: ...num-type _a, [
(output-data:(folded: _num making _total via _total + _num, ..._a) / _a's length)
]))
One's 7 foot 4, one's 4 foot 7. Add 'em up and divide by two, ya get a regular ($mean:7 + 4/12, 4 + 7/12)-foot person.
Writing the code:
The CodeHook, conversely, is where the code of your custom macro is written. You can (set:) temp variables in it, use (if:), (for:), (cond:), and so forth to run different sections of code, and finally output something using either (output:) or (output-data:). (Consult each of those macros' articles to learn the exact means of using them, and their differences.) The temp variables specified by the aforementioned typed variables are automatically set with the passed-in data.
Custom macros can be called like any other macro, by using the variable instead of a name: ($someCustomMacro:)
is how you would
call a custom macro stored in the variable $someCustomMacro, and (_anotherCustomMacro:)
is how you would
call a custom macro stored in the temp variable _anotherCustomMacro.
If you want to use a custom macro throughout your story, the best place to create it is in a "startup" tagged passage. This will aid in testing your story, as those passages' contents are always run first, regardless of the starting passage.
Normally, players can't see inside code hooks, but if an error occurs inside a custom macro, the error message will have an "Open" button allowing the code hook's interior to be viewed. You can take advantage of this by adding (print:) commands inside the code hook, showing you what certain variables contain at certain places in the hook.
Details:
You can, of course, have zero parameters, for a macro that needs no input values, and simply outputs a complicated (or randomised) value by itself.
Currently, (macro:) code hooks do NOT have access to temp variables created outside of the (macro:) call. (set: _name to "Fox")(set:_aCustomMacro to (macro:[(output-data:_name)])) (_aCustomMacro:)
will cause an error, because _name isn't accessible inside the _aCustomMacro macro. They do, however, have access to global variables (which begin with $
).
Much like with typed variables given to (set:) or (put:), each temp variable associated with a parameter is restricted to the given data type. So,
(macro:num-type _a,[(set:_a to 'text')(output-data:_a)]
will cause an error when run.
All custom macros must return some value. If no (output:) or (output-data:) macros were run inside the code hook, an error will result.
If you find yourself writing custom macros that do nothing except call another macro, such as (macro: num-type _a, [(out-data:(range:0,_a))])
,
then you may prefer to use the (partial:) macro instead of (macro:).
See also:
(output:), (output-data:), (partial:)
The (output: ) macro
(output: ) → Changer
Also known as: (out:)
Use this macro inside a (macro:)'s CodeHook to output a command that, when run, renders the attached hook.
Example usage:
(set: $describePotion to (macro: dm-type _potion, [
(size:0.7)+(box:"=XXXXX=")+(border:"solid")+(output:)[\
##(print:_potion's name)
|==
''Hue'': (print:_potion's hue)
''Smell'': (print:_potion's smell)
''Flask'': (print:_potion's flask)
''Effect'': (print: _potion's effect)
==|
//(print: _potion's desc)//
]
]))
($describePotion: (dm:
"name", "Vasca's Dreambrew",
"hue", "Puce",
"smell", "Strong acidic honey",
"flask", "Conical, green glass, corked",
"effect", "The drinker will, upon sleeping, revisit the last dream they had, exactly as it was.",
"desc", "Though Vasca was famed in life for her more practical potions, this brew is still sought after"
+ " by soothsayers and dream-scryers alike.",
))
Rationale:
For more information on custom macros, consult the (macro:) macro's article. All custom macros have inputs and output. This macro lets you output an entire hook, displaying it in a single call of the macro. Attach this to a hook at the end of your custom macro's code hook, and the custom macro will produce a command that displays the hook, similar to how (print:) or (link-goto:) work.
If you want your custom macro to return single values of data, like numbers or arrays, rather than hooks, please use the (output-data:) macro instead.
Details:
As soon as a hook with (output:) attached is encountered, all further macros and code in the CodeHook will be ignored, just as how (go-to:) and (redirect:) behave. This behaviour is unique among changers.
You can combine (output:) with other changers, like (text-style:) or (link:). The hook that is displayed by the command will have those other changers applied to it.
As you might have noticed, (output:) accepts no values itself - simply attach it to a hook.
Attempting to use (output:) outside of a custom macro's CodeHook will cause an error.
As of 3.3.0, custom commands created by (output:) that are stored in story-wide variables can be saved using (save-game:), just like any other value. Be warned, however, that these commands take up space in browser storage proprotional to the number and size of temp variables used inside the custom macro, and the size of the attached hook. If you're concerned about browser storage space, consider limiting the complexity of custom commands you store in story variables.
See also:
The (output-data: ) macro
(output-data: Any) → Instant
Also known as: (out-data:)
Use this macro inside a (macro:)'s CodeHook to output the value that the macro produces.
Example usage:
(set: $randomCaps to (macro: str-type _str, [
(output-data:
(folded: _char making _out via _out + (either:(lowercase:_char),(uppercase:_char)),
..._str)
)
]))
($randomCaps:"I think my voice module is a little bit very broken.")
Rationale:
For more information on custom macros, consult the (macro:) macro's article. All custom macros have inputs and output. This macro specifies the data value to output - provide it at the end of your macro's CodeHook, and give it the value you want the macro call to evaluate to.
This is best suited for macros which primarily compute single data values, like strings, arrays and datamaps. If you wish to output a long span of code, please consider using the (output:) changer instead.
Details:
As soon as an (output-data:) macro is run, all further macros and code in the CodeHook will be ignored, much like how the (go-to:) and (undo:) macros behave.
Attempting to call (output-data:) outside of a custom macro's CodeHook will cause an error.
See also:
The (error: ) macro
(error: String) → Instant
Designed for use in custom macros, this causes the custom macro to immediately produce an error, with the given message string, and ceases running any further code in the CodeHook.
Example usage:
(set: $altCaps to (macro: str-type _input, [
(if: _input is "")[(error: "I can't alt-caps an empty string.")]
(output:
(folded: _char making _result via _result +
(cond: pos % 2 is 0, (lowercase:_char), (uppercase:_char)),
..._input
)
)
]))
($altCaps:"")
Rationale:
Allowing your custom macros to produce insightful error messages is essential to making them user-friendly, especially
if you intend other authors to use them. In the example above, for instance, an empty string inputted to the $altCaps
macro would causes (folded:) to produce an error, as ..._input
would spread zero characters. However, the earlier
custom error provides a better message, explaining exactly what the problem is.
Details:
As with (output-data:), as soon as this is encountered, all further macros and code in the CodeHook will be ignored.
Note that this occurs even if the macro is given as input to another macro - (cond: false, (error:"There's a problem"), "")
will always produce the error, regardless of (cond:)'s behaviour.
If an empty string is given to this macro, an error (different from the intended error) will be produced. Also, attempting to call (error:) outside of a custom macro's CodeHook will cause another (also different from intended) error.
See also:
(output:), (output-data:), (assert:)
The (datatype: ) macro
(datatype: Any) → Datatype
This macro takes any storeable value, and produces a datatype that matches it.
Example usage:
(if: _theirName is a (datatype:_myName))
checks whether or not $theirName is the same type as $myName.(altered: (datatype:_input)-type _n via _n + _input, ..._values)
creates a lambda that only accepts data with the same type as that of the _input variable, and runs (altered:) with it.
Rationale:
This isn't a macro you're likely to commonly use, because most of the time, you have exact knowledge of the
types of data you use throughout your story. But, this can be helpful in custom macros created with (macro:), if they
have any any-type
parameters. Being able to identify the exact type that such a value is allows you to give types to other
data based on that type.
Details:
The only types that this will return are "general" types, like string
, number
, boolean
and such. More specific types
like even
, or descriptive types like empty
, will not be returned, even if it's given a value that matches those types. Nor will
spread datatypes be returned - even if a given string consists only of, say, digits, then ...digits
won't be returned instead of str
.
if there isn't a known datatype value for the given data (for instance, if you give it a HookName) then an error will be produced.
See also:
The (datapattern: ) macro
(datapattern: Any) → Any
This takes any storeable value, and produces a datatype that matches it, in a manner similar to (datatype:). However, when given an array or datamap,
it creates an array or datamap with its values replaced with their datatypes, which can be used as a more accurate pattern with matches
or (set:) elsewhere.
Example usage:
(datapattern: (a:15,45))
produces(a:num,num)
.(datapattern: (passage: ))
produces(dm:"name",str,"source",str,"tags",(a:str))
(as long as the passage has no metadata macros in it).$coordinate matches (datapattern: (a:15,45))
checks if $coordinate is an array of exactly two numbers.(datapattern: $value) matches $value2
checks if $value2 exactly matches the structure of $value.
Details:
The (datatype:) macro is useful for examining and comparing the datatypes of values, but when dealing with arrays and datamaps, each of which can have radically
different purposes and meanings throughout your story, that macro only produces array
or dm
when given them, which isn't too helpful when checking
if one array is similar to another. This macro produces a more precise result - an array or datamap with datatypes replacing its values - which is compatible with
the matches
operator, the (set:) macro, parameters of the (macro:) macro, and other places where datatypes are useful.
Details:
This won't return structures containing spread datatypes, even if those could plausibly describe the passed-in data structure - an array with 26 numbers in it will,
when given to this macro, produce an array containing num
26 times, no more or less.
Note that this does not produce any string patterns, like those produced by (p:) - any string given to this will still result in str
being returned.
See also:
The (partial: ) macro
(partial: String or CustomMacro, [...Any]) → CustomMacro
When given either the string name of a built-in macro, or a custom macro, followed by various values, this creates a custom macro that serves as a shorthand for calling the given macro with those values, plus any additional values.
Example usage:
(set: $zeroTo to (partial:"range", 0))
sets $zeroTo to a custom macro which is a shortcut for calling (range:) with 0 as the first value.($zeroTo: 10)
is the same as(range:0,10)
.($zeroTo: 5)
is the same as(range:0,5)
.(set: $askDLG to (partial:"dialog", bind _result))
sets $askDLG to a custom macro that calls (dialog:) bound to the _result temp variable. This can be used repeatedly to show dialogs that ask input from the player, without having to include the bound variable each time.(set: $next to (partial:"link-goto", "==>"))
creates a custom macro that produces passage links, where the link text is always "==>". Calling($next2: "Continue")
would thus be equivalent to(link-goto: "==>", "Continue")
.(set: $next2 to (partial: $next, "Continue"))
takes the previous example's custom macro, stored in $next, and makes a version where the passage name is always "Continue".(set: $envNoise to (partial:'either',"","",""))
creates a custom macro that randomly chooses between three empty strings and any other values you might give. This could be used for random flavour text in environments:($envNoise:"You hear a jingling windchime")
would only display the text "You hear a jingling windchime" 25% of the time the macro is run.
Rationale:
This is designed as a shorthand for writing certain common macro calls with the same first values over and over. Think of (partial:) as creating a partial macro call - one that isn't finished, but which can be used to make finished calls to that macro, by providing the remaining values.
You may notice that a number of macros in Harlowe have a "configuration-first" ordering of their values - (rotated:) takes the number of rotations first, (sorted:) takes the optional sorting lambda first, (cycling-link:) takes the optional bound variable first, and so forth. This ordering works well with (partial:).
Details:
Don't fall into the trap of thinking the values given to (partial:) will be re-evaluated on each call of the custom macro! For instance,
(partial: "count", (history: ))
will not produce a custom macro that is always equivalent to (count:(history: ), ..._someOtherNumbers)
.
Remember that (history:) produces an array of passage names each time it's called. It is that array that is given to (partial:), so every
call to the produced custom macro will use that array, and not whatever the current (history:) array would be.
Unlike macros created with (macro:), the "params" data name of macros created with (partial:) is always an empty array.
As of 3.3.0, this can not be used with metadata macros such as (metadata:) or (storylet:). Giving "metadata", "storylet" or other such macros' names will produce an error.
See also:
The (a: ) macro
(a: [...Any]) → Array
Also known as: (array:)
Creates an array, which is an ordered collection of values.
Example usage:
(a:)
creates an empty array, which could be filled with other values later.
(a: "gold", "frankincense", "myrrh")
creates an array with three strings.
This is also a valid array, but with its elements spaced in a way that makes them more readable.
(a:
"You didn't sleep in the tiniest bed",
"You never ate the just-right porridge",
"You never sat in the smallest chair",
)
Rationale:
For an explanation of what arrays are, see the Array article. This macro is the primary means of creating arrays - simply supply the values to it, in order.
Details:
Note that due to the way the spread ...
operator works, spreading an array into
the (a:) macro will re-create the original array unchanged: (a: ...$array)
is the same as just $array
.
See also:
The (dm: ) macro
(dm: [...Any]) → Datamap
Also known as: (datamap:)
Creates a datamap, which is a data structure that pairs string names with data values. You should provide a string name, followed by the value paired with it, and then another string name, another value, and so on, for as many as you'd like.
Example usage:
(dm:)
creates an empty datamap.
(dm: "Cute", 4, "Wit", 7)
creates a datamap with two names and values.
The following code also creates a datamap, with the names and values laid out in a readable fashion.
(dm:
"Susan", "A petite human in a yellow dress",
"Tina", "A ten-foot lizardoid in a three-piece suit",
"Gertie", "A griffin draped in a flowing cape",
)
Rationale:
For an explanation of what datamaps are, see the Datamap article. This macro is the primary means of creating datamaps - simply supply a name, followed by a value, and so on.
In addition to creating datamaps for long-term use, this is also used to create "momentary" datamaps which are used only in some operation. For instance, to add several values to a datamap at once, you can do something like this:
(set: $map to (dm:))
(set: $map to it + (dm: "Name 1", "Value 1", "Name 2", "Value 2"))
You can also use (dm:) as a kind of "multiple choice" structure, if you combine it with
the 's
or of
syntax. For instance...
(set: $monsterName to "Slime")
(set: $element to $monsterName of (dm:
"Chilltoad", "Ice",
"Rimeswan", "Ice",
"Brisketoid", "Fire",
"Slime", "Water"
))
...will set $element to one of those elements if $monsterName matches the correct name. But, be warned: if none of those names matches $monsterName, an error will result.
See also:
The (ds: ) macro
(ds: [...Any]) → Dataset
Also known as: (dataset:)
Creates a dataset, which is an unordered collection of unique values.
Example usage:
(ds:)
creates an empty dataset, which could be filled with other values later.
(ds: "gold", "frankincense", "myrrh")
creates a dataset with three strings.
Rationale:
For an explanation of what datasets are, see the Dataset article. This macro is the primary means of creating datasets - simply supply the values to it, in any order you like.
Details:
You can also use this macro to remove duplicate values from an array (though also eliminating the array's
order) by using the spread ...
operator like so: (a: ...(ds: ...$array))
.
See also:
The (all-pass: ) macro
(all-pass: Lambda, [...Any]) → Boolean
Also known as: (pass:)
This takes a "where" lambda and a series of values, and evaluates to true if the lambda, when run using each value, never evaluated to false.
Example usage:
(all-pass: _num where _num > 1 and < 14, 6, 8, 12, 10, 9)
is the same asall of (a:6, 8, 12, 10, 9) > 1 and < 14
.(all-pass: _room where "Egg" is not in _room's objs, ...$rooms)
is true if each datamap in $rooms doesn't have the string"Egg"
in its "objs".
Rationale:
The contains
and is in
operators can be used to quickly check if a sequence of values contains an exact value or values, and, combined with the
all
and some
data names, can check that the values in a sequence merely resemble a kind of value - for instance, that they're positive
numbers, or strings beginning with "E". But, they are times when you're writing the same check over and over, like is an empty or is a whitespace
,
or something more complicated, and would like the ability to store the check in a lambda and reuse it.
The (all-pass:) macro lets you perform these checks easily using a lambda, identical to that used with (find:) - simply write a "temp variable
where
a condition" expression, and every value will be put into the temp variable one by one, and the condition checked for each.
Additionally, you can use (all-pass:) just to run a single "where" lambda against a single value - for instance, as a variation of (if:). This is permitted, too - simply write the lambda and the single value. For those cases, you may wish to write it as (pass:), a shorthand form that visually indicates that you're only checking one value rather than "all".
Details:
Of course, if any condition should cause an error, such as checking if a number contains a number, then the error will appear.
If zero values are given to (all-pass:), then it will return true by default.
The temp variable (if you choose to name it instead of using it
) is controlled entirely by the lambda - it doesn't exist
outside of it, it won't alter identically-named temp variables outside, and you can't manually (set:)
it within the lambda.
You can refer to other variables, including other temp variables, in the where
condition. For instance, you can
write (set: _name to "Eva")(all-pass: _item where _item is _name, "Evan", "Eve", "Eva")
. However, for obvious reasons,
if the outer temp variable is named the same as the lambda's temp variable, it can't be referred to in the condition.
See also:
(sorted:), (count:), (find:), (some-pass:), (none-pass:)
The (altered: ) macro
(altered: Lambda, [...Any]) → Array
This takes a "via" lambda and a sequence of values, and creates a new array with the same values in the same order, but altered via the operation in the lambda's "via" clause. An optional "where" clause can also be provided, which, if its condition is false, causes that particular value to be unchanged.
Example usage:
(altered: _monster via "Dark " + _monster, "Wolf", "Ape", "Triffid")
produces(a: "Dark Wolf", "Dark Ape", "Dark Triffid")
(altered: _player via _player + (dm: "HP", _player's HP - 1), ...$players)
produces an array of $players datamaps whose "HP" datavalue is decreased by 1.(altered: via it * -1 where it is an odd, 1,2,3,4,5,6)
produces(a:-1,2,-3,4,-5,6)
. Because2 is an odd
producesfalse
, the 2 is unaltered, and so forth.
Rationale:
Transforming entire arrays or datasets, performing an operation on every item at once, allows arrays to be modified with the same ease that single values can - just as you can add some extra text to a string with a single +, so too can you add extra text to an entire array of strings using a single call to (altered:).
This macro uses a lambda (which is just the "temp variable via
an expression" expression) to take each item in the sequence and produce a new
value to populate the resulting array. For (altered: _a via _a + 1, 10,20,30)
it will produce 10 + 1, 20 + 1 and 30 + 1, and put those
into a new array.
Details:
Of course, if any operation applied to any of the values should cause an error, such as trying to add a string to a number, an error will result.
An error will NOT appear if you provide no values after the lambda - an empty array will be returned instead.
This allows you to write (altered: $lambda, ...$array)
without checking whether $array contains any values (which
you may not be certain of, if it contains the result of a previous (find:)).
The temp variable (if you choose to name it instead of using it
) is controlled entirely by the lambda - it doesn't exist
outside of it, it won't alter identically-named temp variables outside, and you can't manually (set:)
it within the lambda.
You can refer to other variables, including other temp variables, in the via
expression. For instance, you can write
(altered: _object via _playerName + "'s " + _object, "Glove", "Hat", "Purse")
. However, for obvious reasons,
if the outer temp variable is named the same as the lambda's temp variable, it can't be referred to in the expression.
If no values are given to (altered:) except for the lambda, an empty array will be produced.
See also:
The (count: ) macro
(count: Array or String, ...Any) → Number
Accepts a string or array, followed by a value, and produces the number of times any of the values are inside the string or array.
Example usage:
(count: (a:1,2,3,2,1), 1, 2)
produces 4.(count: "Though", "ugh","u","h")
produces 4.
Rationale:
This can be thought of as an accompaniment to the contains
operator. Usually, you just want to check if one or more occurrences
of the substring or value are in the given container. To check if an array or string contains any or all of the values,
you can use contains
with the all
or some
data names, like so: $arr contains all of (a:1,2)
and $arr contains some of (a:1,2)
. But, if you need an exact figure for the number of occurrences,
this macro will be of use.
A note about newer macros:
(count:) is a fairly old Harlowe macro. Two other macros, (find:) and (str-find:),
exist for checking values and substrings using more powerful constructs, like string patterns (produced by (p:) and its relatives)
or "where" lanbdas. In many cases, you can replicate the functionality of (count:) by using these macros, and checking the length
of the returned array.
For instance, (count: "Abracadabra", "a","b")
is the same as length of (str-find:(p-either:"a","b"),"Abracadabra")
.
But, you may notice that the (count:) call is somewhat shorter and more readable. Thus, if you only need
to perform a simple check of substrings or values, (count:) can be preferable to those other macros.
Details:
If you use this with a number, boolean, datamap, dataset (which can't have duplicates), or anything else which can't have a value, then an error will result.
If you use this with a string, and the values aren't also strings, then an error will result.
Substrings are counted separately from each other - that is, the string "Though" contains "ugh" once and "h"
once, and (count: "Though","ugh","h")
results in 3. To check for "h" occurrences that are not contained in "ugh",
you can try subtracting two (count:)s - (count: "Though","ugh") - (count: "Though","h")
produces 1.
See also:
(find:), (str-find:), (dm-names:), (dm-values:)
The (dm-altered: ) macro
(dm-altered: Lambda, Datamap) → Datamap
Also known as: (datamap-altered:)
This is a variant of (altered:) which takes a "via" lambda and a single datamap, and creates a new datamap with the same datanames, but with the values changed by the 'via' lambda. The 'via' lambda is given a datamap with 'name' and 'value' datanames (identical to those in the array produced by (data-entries:)), so that the name of each data value can be used in the lambda, but it must produce a single data value. An optional "where" clause can also be provided, which, if its condition is false, causes that particular value to be unchanged.
Example usage:
(dm-altered: via its value + 10 where its name is not 'Pluck', (dm: 'Caution', 2, 'Pluck', 5, 'Suspicion', 1))
produces(dm: 'Caution',12,'Pluck',5,'Suspicion',11)
. Note that theit
value in the lambda is(dm:'name', 'Caution', 'value', 2)
for the first loop,(dm:'name', 'Pluck', 'value', 5)
for the second loop, and(dm:'name', 'Suspicion', 'value', 1)
for the third loop.(dm-altered: _a via 1 where _a's value is a num, $dm)
produces a copy of the datamap in $dm, but with all the number values changed to 1.
Rationale:
Generally, datamaps (unlike arrays) are not designed to have all of their values looped over and altered in one go, as each value is meant to have its own distinct meaning relative to the others. But, there are a few situations where this is desirable, such as altering multiple numbers in a statistics datamap to fit a particular range (such as from 1 to 100). This essentially combines (dm-entries:) with (altered:) (or perhaps (folded:)) by letting you operate on each value while also having access to its name, and automates the process of creating the new datamap from the altered (dm-entries:).
Details:
Unlike (altered:), you must supply a datamap as the second value. Additionally, similar to (dm-entries:), only one datamap can be given to this macro. If
you want to alter multiple datamaps with the same lambda, you may want to combine this with (altered:), in a manner
similar to this: (altered: _dm, via (dm-altered: $lambda, _dm), ...$arrayOfDatamaps)
.
Of course, if any operation should cause an error, such as trying to add a string to a number, an error will result.
The temp variable (if you choose to name it instead of using it
) is controlled entirely by the lambda - it doesn't exist
outside of it, it won't alter identically-named temp variables outside, and you can't manually (set:)
it within the lambda.
You can refer to other variables, including other temp variables, in the via
expression. For instance, you can write
(dm-altered: _accessory via _name + "'s " + _accessory's value, "Glove", "Hat", "Purse")
. However, for obvious reasons,
if the outer temp variable is named the same as the lambda's temp variable, it can't be referred to in the expression.
If the given datamap is empty (has no values) then another empty datamap will be returned.
See also:
The (dm-entries: ) macro
(dm-entries: Datamap) → Array
Also known as: (data-entries:), (datamap-entries:)
This takes a datamap, and returns an array of its name/value pairs. Each pair is a datamap that only has "name" and "value" data. The pairs are ordered by their name.
Example usage:
(dm-entries: (dm:'B',24, 'A',25))
produces the following array:(a: (dm: "name", "A", "value", 25), (dm: "name", "B", "value", 24))
(altered: _entry via _entry's name + ":" + _entry's value, ...(dm-entries: $m))
creates an array of strings from the $m datamap's names and values.
Rationale:
There are occasions where operating on just the names, or the values, of a datamap isn't good enough - you'll want both. Rather than the verbose process of taking the (dm-names:) and (dm-values:) arrays and using them (interlaced:) with each other, you can use this macro instead, which allows the name and value of each entry to be referenced using "name" and "value" properties.
See also:
The (dm-names: ) macro
(dm-names: Datamap) → Array
Also known as: (data-names:), (datamap-names:)
This takes a datamap, and returns a sorted array of its data names, sorted alphabetically.
Example usage:
(dm-names: (dm:'B','Y', 'A','X'))
produces the array (a: 'A','B')
Rationale:
Sometimes, you may wish to obtain some information about a datamap. You may want to list all of its data names, or determine how many entries it has. You can use the (dm-names:) macro to do these things: if you give it a datamap, it produces a sorted array of all of its names. You can then (print:) them, check the length of the array, obtain a subarray, and other things you can do to arrays.
See also:
The (dm-values: ) macro
(dm-values: Datamap) → Array
Also known as: (data-values:), (datamap-values:)
This takes a datamap, and returns an array of its values, sorted alphabetically by their name.
Example usage:
(dm-values: (dm:'B',24, 'A',25))
produces the array (a: 25,24)
Rationale:
Sometimes, you may wish to examine the values stored in a datamap without
referencing every name - for instance, determining if 0 is one of the values.
(This can't be determined using the contains
keyword, because that only checks
the map's data names.) You can extract all of the datamap's values into an array
to compare and analyse them using (dm-values:). The values will be sorted by
their associated names.
See also:
The (find: ) macro
(find: Lambda, [...Any]) → Array
This searches through the given values, and produces an array of those which match the given search
test (which is expressed using a temp variable, the where
keyword, and a boolean condition).
If none match, an empty array is produced.
Example usage:
(find: _person where _person is not "Alice", ...$people)
produces a subset of $people not containing the string"Alice"
.(find: _item where _item's 1st is "A", "Thorn", "Apple", "Cryptid", "Anchor")
produces(a: "Apple", "Anchor")
.(find: _num where (_num >= 12) and (it % 2 is 0), 9, 10, 11, 12, 13, 14, 15, 16)
produces(a: 12, 14, 16)
.(find: _val where _val + 2, 9, 10, 11)
produces an error, because_val + 2
isn't a boolean.1st of (find: _room where _room's objs contains "Egg", ...$rooms)
finds the first datamap in $rooms whose "objs" contains the string"Egg"
.
Rationale:
Selecting specific data from arrays or sequences based on a user-provided boolean condition is one of the more common and powerful operations in programming. This macro allows you to immediately work with a subset of the array's data, without caring what kind of subset it is. The subset can be based on each string's characters, each datamap's values, each number's evenness or oddness, whether a variable matches it... anything you can write.
This macro uses a lambda (which is just the "temp variable where
a condition" expression) to check every one of
the values given after it. For (find: _item where _item > 40, 30, 60, 90)
, it will first check if 30 > 40
(which
is false
), if 60 > 40
(which is true
), and if 90 > 40
(which is true
), and include in the returned array
those values which resulted in true
.
Details:
Of course, if any condition should cause an error, such as checking if a number contains a number, then the error will appear.
However, an error will NOT appear if you provide no values after the lambda - searching an empty sequence will simply
result in an empty array being returned. This allows you to write (find: $lambda, ...$array)
without checking whether $array contains
any values (which you may not be certain of, if it contains the result of a previous (find:)).
The temp variable (if you choose to name it instead of using it
) is controlled entirely by the lambda - it doesn't exist
outside of it, it won't alter identically-named temp variables outside, and you can't manually (set:)
it within the lambda.
You can refer to other variables, including other temp variables, in the where
condition. For instance, you can
write (set: _name to "Eva")(find: _item where _item is _name, "Evan", "Eve", "Eva")
. However, for obvious reasons,
if the outer temp variable is named the same as the lambda's temp variable, it can't be referred to in the condition.
There isn't a way to examine the position of a value in the condition - you can't write, say, (find: _item where _pos % 2 is 0, "A", "B", "C", "D")
to select just "B" and "D".
You shouldn't use this macro to try and alter the given values! Consider the (altered:) or (folded:) macro instead.
See also:
(sorted:), (all-pass:), (some-pass:), (none-pass:)
The (folded: ) macro
(folded: Lambda, ...Any) → Any
This takes a "making" lambda and a sequence of values, and creates a new value (the "total") by feeding every value in the sequence to the lambda, akin to folding a long strip of paper into a single square. The first value after the lambda is put into the total (which is the variable inside the lambda's "making" clause) before running the lambda on the remaining values.
Example usage:
(folded: _enemy making _allHP via _allHP + _enemy's HP, 0, ...$enemies)
will first set _allHP to 0, then add $enemies's 1st's HP to it, then add the remaining HP values to it. Then it will return the number in _allHP.(folded: _name making _allNames via _allNames + "/" + _name, ...(history: ))
is the same as(joined: "/", ...(history: ))
.
Rationale:
The (for:) macro, while intended to display multiple copies of a hook, can also be used to run a single macro call multiple times. You may wish to use this to repeatedly (set:) a variable to itself plus one of the looped values (or some other operation). (folded:) is meant to let you perform this in a shorter, more fluid fashion.
Consider, first of all, a typical (for:) and (set:) loop such as the following:
(set:$enemies to (a:(dm:"Name","Mossling", "HP",7), (dm:"Name","Moldling","HP",2)))
{(set:_allHP to 0)
(for: each _enemy, ...$enemies)[
(set:_allHP to it + _enemy's HP)
]}
TOTAL HEART POINTS: _allHP
This can be rewritten using (folded:) as follows. While this version may seem a little harder to read if you're not used to it, it allows you to accomplish the same thing in a single line, by immediately using the macro's provided value without a variable:
(set:$enemies to (a:(dm:"Name","Mossling", "HP",7), (dm:"Name","Moldling","HP",2)))
TOTAL HEART POINTS: (folded: _enemy making _allHP via _allHP + _enemy's HP, 0, ...$enemies)
If you need to perform this operation at various different times in your story, you may wish to (set:) the lambda into a variable, so that you, for instance, might need only write:
(set:$enemies to (a:(dm:"Name","Mossling", "HP",7), (dm:"Name","Moldling","HP",2)))
(set: $totalEnemyHP to (_enemy making _allHP via _allHP + _enemy's HP))
TOTAL HEART POINTS: (folded: $totalEnemyHP, 0, ...$enemies)
Details:
Let's look at this example usage again.
(set:$enemies to (a:(dm:"Name","Mossling", "HP",7), (dm:"Name","Moldling","HP",2)))
(folded: _enemy making _allHP via _allHP + _enemy's HP, 0, ...$enemies)
This macro call uses a "making" lambda (which is the "temp variable making
another temp variable via
expression" expression) to run the
expression using every provided value, much like those repeated (set:) calls. The temp variable in the "making" clause, _allHP
, is the total,
and at the start, it is set to the first provided value (in this case, 0). The temp variable at the start, _enemy
, is then set to the next value
after that (which in this case would be the "Mossling" datamap), and the "via" clause is used to set _allHP
to a new value.
This repeats until all of the values have been handled. Then, the final result of _allHP
is returned.
Of course, if at any time the expression should cause an error, such as adding a number to a string, then an error will result.
Both of the temp variables can be named anything you want. As with other lambda macros, they don't exist outside of it, won't alter identically-named temp variables outside of it, and can't be manually (set:) within the lambda.
You can refer to other variables, including other temp variables, in the via
expression. For instance, you can write
(folded: _score making _totalScore via _totalScore + _score * _bonusMultiplier)
. However, for obvious reasons,
if the outer temp variable is named the same as the lambda's temp variables, it can't be referred to in the expression.
You can also use a "where" clause inside the "making" lambda to prevent an operation from occurring if a value isn't suitable -
(folded: _item making _total via _total + _item where _item > 0, 0, ...$arr)
will only sum up the values in $arr which are greater than 0.
Note that (as of 3.3.6), the "where" clause does not apply to the first value (as that is put into _total
from the outset, and never becomes an _item
value). As such,
an explicit 0 is given before ...$arr
, so that ...$arr
's first value is still subject to the "where" clause.
See also:
The (interlaced: ) macro
(interlaced: Array, ...Array) → Array
Takes multiple arrays, and pairs up each value in those arrays: it creates an array containing each array's first value followed by each array's second value, and so forth. If some values have no matching pair (i.e. one array is longer than the other) then those values are ignored.
Example usage:
(interlaced: (a: 'A', 'B', 'C', 'D'), (a: 1, 2, 3))
is the same as (a: 'A',1,'B',2,'C',3)
Rationale:
There are a couple of other macros which accept data in pairs - the most notable being (dm:), which takes data names and data values paired. This macro can help with using such macros. For instance, you can supply an array of (dm-names:) and (dm-values:) to (interlaced:), and supply that to (dm:), to produce the original datamap again. Or, you can supply just the names, and use a macro like (repeated:) to fill the other values.
However, (interlaced:) can also be of use alongside macros which accept a sequence: you
can use it to cleanly insert values between each item. For instance, one can pair
an array with another array of spaces, and then convert them to a string with (str:).
(str: ...(interlaced: $arr, (repeated: $arr's length, ' ')))
will create a string containing
each element of $arr, followed by a space.
Details:
If one of the arrays provided is empty, the resulting array will be empty, as well.
See also:
The (none-pass: ) macro
(none-pass: Lambda, ...Any) → Boolean
This can be thought of as the opposite of (all-pass:): it produces true if every value, when given to the lambda, never evaluated to true. If zero values are given to (none-pass:), then it will return true by default, just like (all-pass:). For more information, consult the description of (all-pass:).
Example usage:
(set: $partyMembers to (a: (dm: "name", "Alan", "curseLevel", 0), (dm: "name", "Jess", "curseLevel", 0)))
(set: $noMelvins to (none-pass: where its name is "Melvin", ...$partyMembers))
The (permutations: ) macro
(permutations: ...Any) → Array
When given a sequence of values, this produces an array containing each permutation of the order of those values, as arrays.
Example usage:
(permutations: "☆", "♡", "∪")
produces(a:(a:"☆","♡","∪"),(a:"♡","☆","∪"),(a:"∪","☆","♡"),(a:"☆","∪","♡"),(a:"♡","∪","☆"),(a:"∪","♡","☆"))
.
Rationale:
If you're writing an algorithm that cares about combinations of data, such as a procedurally generated puzzle or password, you may find that this macro has
a number of subtle uses. This macro by itself provides an easy way to check if a sequence of values contains exactly the same values as
another sequence, regardless of order. For instance, you can check if another array stored in $array
contains exactly two 3s, two 2s and one 1 by
writing (permutations:3,1,3,2,2) contains $array
, because if it was so, the array would be included among those permutations. You can't perform
this check by writing (dataset:3,1,3,2,2) is (dataset:...$array)
because datasets, by design, don't hold multiples of a single value (such as 3).
Additionally, this macro can be combined with (find:) and (shuffled:) to help you find a permutation that matches certain criteria. For instance,
to find a random permutation of the numbers 0 to 5 that doesn't begin with 0, you can write
1st of (shuffled: ...(find: where its 1st is not 0, ...(permutations:0,1,2,3,4)))
. While this could be performed by simply re-running
(shuffled:0,1,2,3,4)
until it produced an array that didn't begin with 0, the code to check and re-run this would be much more complicated.
Details:
When given no values, this simply returns the empty array (a:)
.
See also:
The (range: ) macro
(range: Number, Number) → Array
Produces an array containing an inclusive range of whole numbers from a to b, in ascending order.
Example usage:
(range:1,14)
is equivalent to(a:1,2,3,4,5,6,7,8,9,10,11,12,13,14)
(range:2,-2)
is equivalent to(a:-2,-1,0,1,2)
Rationale:
This macro is a shorthand for defining an array that contains a sequence of integer values. Rather than writing out all of the numbers, you can simply provide the first and last numbers.
Details:
If the second number given is smaller than the first number, then (range:) will act as if their positions
were reversed - that is, (range:50,0)
is the same as (range:0,50)
.
Certain kinds of macros, like (either:) or (dataset:), accept sequences of values. You can
use (range:) with these in conjunction with the ...
spreading operator:
(dataset: ...(range:2,6))
is equivalent to (dataset: 2,3,4,5,6,7)
, and
(either: ...(range:1,5))
is equivalent to (random: 1,5)
.
See also:
The (repeated: ) macro
(repeated: Number, ...Any) → Array
When given a number and a sequence of values, this macro produces an array containing those values repeated, in order, by the given number of times.
Example usage:
(repeated: 5, false)
produces(a: false, false, false, false, false)
(repeated: 3, 1,2,3)
produces(a: 1,2,3,1,2,3,1,2,3)
Rationale:
This macro, as well as (range:), are the means by which you can create a large array of
similar or regular data, quickly. Just as an example: you want, say, an array of several
identical, complex datamaps, each of which are likely to be modified in the game,
you can use (repeated:) to make those copies easily. Or, if you want, for instance, a
lot of identical strings accompanied by a lone different string, you can use (repeated:)
and add a (a: "string")
to the end.
When you already have an array variable, this is similar to simply adding that variable to itself several times. However, if the number of times is over 5, this can be much simpler to write.
Details:
An error will, of course, be produced if the number given is negative, or contains a fraction. As of 3.2.0, however, it will no longer error if the number is 0.
If you wish to repeat a string multiple times, please use (str-repeated:).
See also:
(a:), (range:), (str-repeated:)
The (reversed: ) macro
(reversed: [...Any]) → Array
Similar to (a:), except that it creates an array containing the elements in reverse order.
Example usage:
(set: $a to (reversed: ...$a, 2))
sets $a to its reverse, with 2
at the start.
Rationale:
Having stored items in an array, or obtained a built-in array like (history:), you may want to perform some action using it - maybe assemble them into a single string using (folded:) - in the opposite order to which they are stored. (reversed:) allows this reversal to be easily created.
Details:
Unlike (shuffled:), which produces an error if one or no elements are given, this does not error if a non-reversible sequence of one or zero is given. This is meant to permit its wider use with data whose length you may not always have control over, such as the (history:) array.
If you wish to specifically reverse the characters in a string, please use (str-reversed:).
See also:
(a:), (shuffled:), (rotated:), (str-reversed:)
The (rotated-to: ) macro
(rotated-to: Lambda, [...Any]) → Array
Similar to the (a:) macro, but it also takes a "where" lambda at the start, and cycles the order of the subsequent values so that the first value to match the lambda is placed at the start.
Example usage:
(rotated-to: where it is 'D', 'A','B','C','D')
is equal to(a: 'D','A','B','C')
.(rotated-to: where it > 3, 1, 2, 3, 4, 5)
is equal to(a: 4, 5, 1, 2, 3)
.
Rationale:
This is a variation of the (rotated:) macro. Both of these macros allow you to cycle through a sequence of values, wrapping back to the start, until a certain value is at the front, then provide an array of the values in that order. The former macro lets you specify an exact number of rotations to do; this one lets you specify what kind of value should be at the front, if you don't know the exact order of the passed-in strings (which may be the case if they come from an array).
Note that while the lambda argument provides a lot of flexibility, if you simply want to compare each value to a known
value, where it is
(such as in an example above) is a simple enough lambda formulation to do so.
Details:
If the lambda doesn't match any of the values (that is, there's no value to rotate to) then an error will result.
As of 3.3.0, you may give only one item after the lambda without causing an error (although an error will still occur if the given lambda doesn't match it).
See also:
(sorted:), (rotated:), (find:)
The (rotated: ) macro
(rotated: Number, ...Any) → Array
Similar to the (a:) macro, but it also takes a number at the start, and moves each item forward by that number, wrapping back to the start if they pass the end of the array.
Example usage:
(rotated: 1, 'A','B','C','D')
is equal to(a: 'D','A','B','C')
.(rotated: -2, 'A','B','C','D')
is equal to(a: 'C','D','A','B')
.
Rationale:
Sometimes, you may want to cycle through a sequence of values, without repeating any until you reach the end. For instance, you may have a rotating set of flavour-text descriptions for a thing in your story, which you'd like displayed in their entirety without the whim of a random picker. The (rotated:) macro allows you to apply this "rotation" to a sequence of data, changing their positions by a certain number without discarding any values.
Remember that, as with all macros, you can insert all the values in an existing
array using the ...
syntax: (set: $a to (rotated: 1, ...$a))
is a common means of
replacing an array with a rotation of itself.
Think of the number as being an addition to each position in the original sequence - if it's 1, then the value in position 1 moves to 2, the value in position 2 moves to 3, and so forth.
Incidentally... you can also use this macro to rotate a string's characters, by doing
something like this: (str: ...(rotated: 1, ...$str))
Details:
As of 3.3.0, providing fewer than two items to this macro will not result in an error (even though that isn't enough values to meaningfully rotate).
As of 3.3.0, rotating by a number of positions greater (or, if negative, less) than the number of values will still result in that many rotations occurring, without an error being produced.
If you can't reliably know how many positions you wish to rotate, but know that you need a certain value to be at the front, simply use the (rotated-to:) variant of this macro instead.
See also:
The (shuffled: ) macro
(shuffled: ...Any) → Array
Similar to (a:), this produces an array of the given values, except that it randomly rearranges the elements instead of placing them in the given order.
Example usage:
(if: $condiments is not an array)[(set: $condiments to (shuffled: "mustard", "mayo", "chili sauce", "salsa"))]
You reach into the fridge and grab... (nth: visits, ...$condiments)? OK.
Rationale:
If you're making a particularly random story, you'll often want to create a 'deck' of random descriptions, elements, etc. that you can use repeatedly. That is to say, you'll want to put them in a random order in an array, preserving that random order for the duration of a game.
The (either:) macro is useful for selecting an element from an array randomly
(if you use the spread ...
syntax), but isn't very helpful for this particular problem. Additionally,
the random
data name of arrays can be used to retrieve a random value, and can remove that value using
(move:), but it isn't as effective if you want that value to remain in the deck after being used.
The (shuffled:) macro is another solution: it takes elements and returns a randomly-ordered array that can be used as you please.
Details:
This is one of the features that uses Harlowe's pseudo-random number generator. If you use (seed:) at the start of the story, the order will be predetermined based on the seed string, and how many other random macros and features have been used before it.
As of 3.3.0, giving zero or more values to (shuffled:) will cause an empty array (such as by (a:)
) to be returned,
rather than causing an error to occur.
See also:
The (some-pass: ) macro
(some-pass: Lambda, ...Any) → Boolean
This is similar to (all-pass:), but produces true if one or more value, when given to the lambda, evaluated to true.
It can be thought of as shorthand for putting not
in front of (none-pass:).
If zero values are given to (all-pass:), then it will return false by default.
For more information, consult the description of (all-pass:).
Example usage:
(set: $partyMembers to (a: (dm: "name", "Alan", "curseLevel", 0), (dm: "name", "Jess", "curseLevel", 0)))
(set: $taintedParty to (some-pass: where its curseLevel > 0, ...$partyMembers))
The (sorted: ) macro
(sorted: [Lambda], ...Any) → Array
This macro produces an array in which the values are sorted in English alphanumeric sort order. If any of the values are not numbers or strings, a "via" lambda must be given first, which is used to translate the value into a number or string that it should be sorted by.
Example usage:
(set: $a to (a: 'A','C','E','G', 2, 1))(sorted: ...$a)
produces(a:1,2,"A","C","E","G")
.(sorted: via its name, ...$creatures)
sorts the datamaps in the array stored in $creatures by their "name" values. Datamaps with an alphanumerically earlier "name" appear first.(sorted: via its length * -1, "Gus", "Arthur", "William")
produces(a: "William", "Arthur", "Gus"))
. This lambda produces negative numbers for each string.(sorted: via its tags's length, ...(passages: ))
produces a version of the (passages:) array, sorted by ascending number of tags each passage datamap has.(sorted: via (passage:it)'s exclusivity, ...(history: ))
produces a version of the (history:) array (which is an array of passage name strings), sorted by the (exclusivity:) metadata the passage with that name has.(sorted: via its h, orange, red, blue, yellow)
produces(a: red, orange, yellow, blue)
.(sorted: via (random:1,100), ...$arr)
is mostly the same as(shuffled:...$arr)
.(sorted: via its urgency, ...(sorted: via its exclusivity, ...(passages: )))
sorts the (passages:) array by urgency, and sorts ties by exclusivity.
Rationale:
The main purpose of arrays is to store data values in a specific order - feats the player has performed,
names of open storylets from (open-storylets:), visited passage names from (history:), names of file slots as produced
by (dm-entries:(saved-games: ))
, to name just a few examples. However, there are times when you want to work with
the same array in a different order, either because the default ordering isn't to your needs - for instance, you wish to list open
storylets by one of their metadata values - or you need to include special exceptions to the normal ordering - for instance, you want to sort
(history:) passages with a certain tag higher than others. This macro can be used to create a sorted array,
organising the given values in either alphanumeric order, or by a particular alphanumeric key.
Details:
The optional "via" lambda must translate each value into either a number or string - otherwise, it will produce an error. It can be provided even if any of the values are already numbers or strings.
The values are sorted as if they were the value that the "via" lambda produced. In the example of (sorted: via its length * -1, "Gus", "Arthur", "William")
. the string
"Gus" is sorted as if it was "Gus"'s length * -1
(which is -3), "Arthur" is sorted as if it was "Arthur"'s length * -1
(which is -6),
and "William" is sorted as if it was "William"'s length * -1
(which is -7). This allows a variety of sorting options.
Datamaps may be sorted by any one of their string or number values, and strings may be sorted in different ways than just their alphanumeric order.
Values sorted by a "via" lambda, but which have the same value to that lambda, are kept in the same order. This is known as a "stable" sort.
(sorted:via its 1st, 'Bob', 'Alice', 'Blake', 'Bella', 'Bertrude')
, which only sorts the strings by their first letter,
will always produce (a:"Alice","Bob","Blake","Bella","Bertrude")
, even though "Blake" is alphabetically sooner than "Bob". This means that, if one needs
to sort an array of datamaps by multiple values (such as sorting a set of characters by name, then by age),
Unlike other programming languages, strings (either produced by the "via" lambda, or sorted by themselves when no lambda was given) aren't sorted using ASCII sort order, but alphanumeric sorting: the string "A2" will be sorted after "A1" and before "A11". Moreover, if the player's web browser supports internationalisation (that is, every current browser except IE 10), then the strings will be sorted using English language rules (for instance, "é" comes after "e" and before "f", and regardless of the player's computer's language settings. Otherwise, it will sort using ASCII comparison (whereby "é" comes after "z").
If there is no "via" lambda, and a non-string, non-number is given to this macro, an error will be produced.
Currently there is no way to specify an alternative language locale to sort by, but this is likely to be made available in a future version of Harlowe.
As of 3.3.0, giving zero or more values (after the optional lambda) to (sorted:) will cause an empty array (such as by (a:)
) to be returned,
rather than causing an error to occur.
See also:
The (subarray: ) macro
(subarray: Array, Number, Number) → Array
When given an array, this returns a new array containing only the elements whose positions are between the two numbers, inclusively.
Example usage:
(subarray: $a, 3, 7)
is the same as$a's (a:3,4,5,6,7)
or$a's 3rdto7th
(subarray: $a, 3, $b)
is the same as$a's (range: 3, $b)
if $b is positive.
Rationale:
This macro may seem redundant, as you can obtain subarrays of arrays without this macro, by using the
's
or of
syntax along with either a specified range of consecutive positions, or an array of
arbitrary position numbers. For instance, $a's 4thto12th
obtains a subarray of $a containing
its 4th through 12th values, $a's (a:1,3,5)
obtains a subarray of $a
containing just its 1st, 3rd and 5th positions, and $a's (range:1, $b)
obtains a subarray of each
position up to $b.
However, in the specific situation where you want to use a variable negative position, counting from the end of the array,
there isn't a succinct option using that syntax. When gathering each value in array $a
between position 1 and $b, where $b is a negative position counting from the end, (range:1, $b)
doesn't work, and
the best you can do without this macro is something like $a's (range: 1, $b + $a's length)
. So, this
macro can be used as a slightly shorter alternative, by writing (subarray: $a, 1, -$b)
.
Details:
As mentioned above, if you provide negative numbers, they will be treated as being offset from the end
of the array - -2
will specify the 2ndlast
item, just as 2 will specify the 2nd
item.
If the last number given is larger than the first (for instance, in (subarray: (a:1,2,3,4), 4, 2)
)
then the macro will still work - in that case returning (a:2,3,4) as if the numbers were in
the correct order.
See also:
The (unique: ) macro
(unique: ...Any) → Array
When given a sequence of values, this produces an array containing each unique value once, in the order that they appeared.
Example usage:
(unique: 1,2,1,2,3,5,5,6,3)
produces(a: 1,2,3,5,6)
(unique: ...(history: ))
produces an array listing the name of every passage currently visited, in the order they were first visited.(reversed: ...(unique: ...(reversed: ...(history: ))))
produces an array listing the name of every passage currently visited, in the order they were last visited. This does so by reversing the array before spreading it into (unique:), then un-reversing it.(unique: ...(altered: via its address, ...$emails))
produces an array of every unique address from among the datamaps in $emails, in the same order.
Rationale:
Arrays are used to hold values whose ordering matters, such as the sequentially visited passages in the array that (history:) produces.
Sometimes, though, you want to eliminate duplicate data from the array in order to use it for some purpose. For instance, you may
want to show a list (using (for:)) of every passage the player has visited, in the order they've visited, but without duplicate entries in the
list. While (dataset:) and the spread ...
syntax can be used to eliminate duplicate entries from an array, such as by (a:...(ds: ...(history: )))
,
this has a small problem: datasets only hold unordered data, and when the dataset is spread using ...
, the values are sorted instead of
in their original order. (unique:) provides an easier method of removing duplicates from an a sequence of values.
Details:
Two values are considered unique if the is
operator, when placed between them, would produce false
. This is the same method of uniqueness
used by datasets.
When given no values, this simply returns the empty array (a:)
. When given values that are all unique, this returns an array of all the
values, with no error occurring.
See also:
The (unpack: ) macro
(unpack: ...VariableToValue) → Instant
A specialised variation of (put:) that lets you extract multiple values from an array, datamap or string, at once, and put them into
multiple variables, by placing a matching data structure on the right of into
containing variables at
the positions of those values. For instance, (unpack: (a: 1, 2, 3) into (a: $x, 2, $y))
sets $x to 1 and $y to 3,
and (unpack: (dm: "B", 3, "A", 1) into (dm: "A", $x, "B", $y))
sets $x to 1 and $y to 3.
Example usage:
(unpack: (a:"north","northeast","south") into (a: _mainPath, _sidePath, _backPath))
sets three temp variables, by overwriting each variable in the array on the right with its matching value in the array on the left.(unpack: $characterStats into (dm: "Maths", _Maths, "Science", _Science))
is the same as(set: _Maths to $characterStats's Maths, _Science to $characterStats's Science)
.(unpack: "The Safecracker" into (p:"The ", str-type _job))
extracts the string "Safecracker" from the value, and puts it in the variable _job.(unpack: (a: "Daria", 25, 14, 25) into (a: str, ...num-type $stats))
extracts the numbers from the left side into a new array stored in $stats.
Rationale:
Extracting values from data structures into variables using just the (set:) and (put:) macros can be rather cumbersome, especially if you need to extract
values from the same array or datamap. The (unpack:) macro provides a means to efficiently access multiple values in such structures, by describing the locations of those values within
the structure - if you want to obtain the first, second, and fourth values in an array and put them into $a, $b and $c, just write (a: $a, $b, any, $c)
, in exactly those positions,
to the right of into
. The visual clarity of this can provide great assistance in understanding and reminding you of what the data structure is, and the relationship of the destination variables to
their source values.
The (unpack:) macro also lets you use string patterns (produced by (p:) and other such macros) to unpack strings into multiple components. To obtain all the digit characters at the
start of a string, and nothing else, and put them into $a, just write (p: ...digit-type $a)
. No long-winded (for:) loops and individual character checks are needed - simply describe the
string using the pattern macros, using typed variables to mark parts to extract, and they can be easily extracted.
Details:
Harlowe checks that each value on the right side (henceforth called the "pattern side") has a value that matches it (using the same rules as the matches
operator)
on the left side (the "data side"), and overwrites the pattern side with the data side, causing the variables at various positions in the pattern side to be overwritten with values from
the data. (Remember that datamaps' "positions" are determined by their datanames, not their locations in the (dm:) macro that created them, as,
unlike arrays, they are not sequential.)
For extracting substrings from strings, use the (p:) macro and its related macros to construct a string pattern, resembling
array patterns. For instance, (unpack: "Slime Ball" into (p: (p-either: "Silt", "Mud", "Peat", "Slime")-type _element, " ", (p-either: "Ball", "Blast", "Shot", "Beam")-type _shape))
extracts the words "Slime" and "Ball" from the value, and puts them in the _element and _shape temp variables. Note that
when this is done, the _element variable is restricted to (p-either: "Silt", "Mud", "Peat", "Slime")-type
data, so putting
any other kind of string into it will cause an error. Generally, it's recommended to use temp variables for string destructuring, and then,
if you need more general variables to hold the extracted substrings, place them in a less restricted variable afterward.
Note that the pattern side's data structure can have any number of nested data structures inside it.
(unpack: (a: (a: 1), 2, (dm: "A", 3)) into (a: (a: $x), 2, (dm: "A", $y)))
also sets $x to 1 and $y to 3. If you need to reach deeply
into a data structure (such as one produced by (passages:), (saved-games:) or (open-storylets:)) to get a certain set of values,
this can come in handy.
You may have noticed that the data structures on the pattern side may have values that aren't variable names, such as the 2 in the
preceding example. These can be used as error-checking values - if a matching value isn't present on the right side at that same position,
then an error will be reported. To ensure that the data side does indeed contain the values you expect it to, you may include these
values in the pattern side. Of course, you may want to simply enforce that a value of a given datatype is in that position, rather a specific
value - this can be done by placing a datatype name at that position, like num
or str
or empty
. Consult the datatype article for more
information on datatype names.
As you may have also noticed, this syntax is convenient for extracting values from the left side of arrays. But, what if you wish to only select
values from the middle, or to skip certain values without putting them in variables? You could use a value or a datamap name at that position,
such as by writing (set: (a: num, $y, $x) to $array)
- though, if you aren't even certain of the data types that could be at those positions,
you may find the special any
data type to be a big help - (set: (a: any, $y, $x) to $array)
sets $x to the 3rd value in $array and $y to the
2nd value, without needing to worry about what the first value might be.
(When dealing with string patterns, the equivalent of any
is simply str
, as strings can't contain non-string data.)
This syntax can be combined with typed variables - simply place the typed variable where a normal variable would be
within the pattern side. (unpack: $array into (a: num, num-type $y, num-type _x))
not only sets $y and _x, but it also
restricts them to number data, all in one statement. If the typed variable is inside an array, and involves a spread datatype (like ...num
)
then it is restricted to data that matches (a: ...num)
(i.e. arrays of zero or more num
values), and it automatically gathers multiple values from the
right-hand-side that match the datatype. (set: (a: str, ...bool-type $examAnswers) to (a: "ANSWER KEY", true, false, false, true, false))
sets $examAnswers to (a:true, false, false, true, false)
.
If the destination doesn't contain any variables - for instance, if you write (unpack: (a:2,3) into (a:3,2))
- then an error will be printed.
For obvious reasons, (unpack:) can't be used with datasets - you'll have to convert them to arrays on the right side.
See also:
The (current-date: ) macro
(current-date: ) → String
This date/time macro produces a string of the current date the current player's system clock, in the format "Thu Jan 01 1970".
Example usage:
Right now, it's (current-date:).
The (current-time: ) macro
(current-time: ) → String
This date/time macro produces a string of the current 12-hour time on the current player's system clock, in the format "12:00 AM".
Example usage:
The time is (current-time:).
The (monthday: ) macro
(monthday: ) → Number
This date/time macro produces a number corresponding to the day of the month on the current player's system clock. This should be between 1 (on the 1st of the month) and 31, inclusive.
Example usage:
Today is day (monthday:).
The (weekday: ) macro
(weekday: ) → String
This date/time macro produces one of the strings "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" or "Saturday", based on the weekday on the current player's system clock.
Example usage:
Today is a (weekday:).
The (ignore: ) macro
(ignore: [...Any]) → Command
If you want to test your passage while ignoring a specific command macro in it, temporarily change that command macro's name to (ignore:), and it will ignore all of the data given to it.
Example usage:
(ignore: ?ghost, (text-style:'outline'))
is an (enchant:) macro that has been temporarily changed to (ignore:), so that the
passage may be tested without the style being applied.
Rationale:
If you want to quickly test some aspect of your passage, you may wish to remove one or more of the commands in it, such as (enchant:) or (hide:). These commands can have a large and cumbersome set of data given to them, and removing and adding them can be bothersome. The (ignore:) macro can be of assistance here: simply change the command's name to "ignore", and it will do nothing, while NOT causing an error regardless of what sort of data is given to it. Then, you can quickly change it back to the original name after testing.
Details:
While it will ignore all well-formed data given to it, (ignore:) will NOT suppress errors that are already present in the data.
For instance, (ignore: 4 + "2")
will still cause an error.
This command can have changers attached, but will, of course, ignore them.
See also:
The (test-true: ) macro
(test-true: [...Any]) → Changer
If you want to test your passage, while ignoring a specific changer macro in it, temporarily change that changer macro's name to (test-true:), and it will ignore all of the data given to it, while enabling the hook.
Example usage:
(test-true: $eggs is 1)[Only one egg remaining!]
features an (if:) macro that has been temporarily changed to (test-true:).
Rationale:
While testing your passage, you may wish to examine what would happen if a changer, such as (if:) or (else:), were to have no effect on its hook. But, removing and adding the macro from your passage code may get tedious and error-prone, especially if you need to disable several such changers at once. Instead, you can simply temporarily change the macro's name to (test-true:), and change it back later. Regardless of what data is given to this macro (colour data for (bg:), booleans for (if:), hooks for (replace:)), this macro won't cause an error.
Details:
While it will ignore all well-formed data given to it, (test-true:) will NOT suppress errors that are already present in the data.
For instance, (test-true: 5 + "3")[]
will still cause an error.
If (test-true:) and another changer are added together, such as in (test-true:)+(if:visits is 1)
, then the latter changer will take precedence and
override it.
See also:
The (test-false: ) macro
(test-false: [...Any]) → Changer
If you want to test your passage in order to see what would happen if an (if:), (unless:) or (else-if:) macro would hide the hook it's attached to,
you can temporarily change the name of the macro to (test-false:), which causes it to ignore the data given to it and act as if it was given false
.
Example usage:
(test-false: $eggs is 1)[Only one egg remaining!]
features an (if:) macro that has been temporarily changed to (test-false:).
Rationale:
This is a counterpart of (test-true:), designed specifically for testing hooks with (if:), (unless:) and (else-if:) changers attached. For most changers, using (test-true:) is sufficient to temporarily suppress the effect of the changer. However, if you want the hook to remain hidden by default during the test, then using (test-true:) would still cause the hook to be displayed. While you could temporarily attach (hidden:) to the hook as well, this can be cumbersome, especially if that would involve adding an additional changer to a long sequence of changers attached to that hook. (test-false:) provides a more convenient alternative.
Details:
While it will ignore all well-formed data given to it, (test-false:) will NOT suppress errors that are already present in the data.
For instance, (test-false: 5 + "3")[]
will still cause an error.
If (test-false:) and another changer are added together, such as in (test-false:)+(if:visits is 1)
, then the latter changer will take precedence and
override it.
See also:
The (assert: ) macro
(assert: Boolean) → Instant
A debugging macro that produces a helpful error whenever the expression given to it produces Boolean false. Use this when testing your story to help ensure that certain facts about the game state are true or not.
Example usage:
(assert: $isWounded is $isBleeding or $isBandaged)
ensures that if $isWounded is true, one of $isBleeding or $isBandaged MUST be true, and if it's false,
both of them MUST be false. Otherwise, an error is produced.
Rationale:
Harlowe's debug mode provides panels to check the current game state, the contents of variables, active enchantments, and so forth, providing assistance in identifying bugs. Of course, knowing what variables contain is not the same as knowing whether the relationships between them are being maintained, and a way of encoding these relationships, in your story, can provide an additional layer of security when debugging your game.
(assert:) allows you to assert facts about the game state, facts which absolutely must be true, so much so that
an error should be produced. For example, if your story's code assumes that the variable $nails will always be smaller than or equal to $maxNails, and you want to
ensure that no bugs are written that cause $nails to be greater, you may write (assert: $nails is <= $maxNails)
, and place that call in
a "debug-header" tagged passage. Thus, should a bug ever appear which causes these variables to no longer maintain their relationship, the (assert:) call will produce an error.
Details:
Note that there are other tools within Harlowe to ensure that variables are obeying certain restrictions, which make the need for certain simple (assert:) calls
unnecessary. Chief among these features is TypedVars, which can be provided to (set:) to permanently and automatically restrict a certain variable to a narrow range
of data. Instead of writing (assert: $petals is an int)
, you can change the earliest (set:) call that creates $petals to (set: int-type $petals to 0)
.
Though this is classed as a "debugging" macro, this works even outside of debug mode.
This can also be useful within custom macros, as a shortened form of combining (if:) with (error:). However, the error message produced by (assert:) may not be as insightful as the customisable error message given to (error:), so it's not especially recommended for use within custom macros that are meant for other authors to use.
See also:
The (assert-exists: ) macro
(assert-exists: HookName or String) → Command
A debugging macro that confirms whether a hook with the given name, or passage text matching the given string, is present in the passage. If not, it will produce a helpful error. Use this to test whether enchantment macros like (click:), (enchant:) or (show:) are working properly.
Example usage:
(assert-exists: "the auroch")
will produce an error if the text "the auroch" isn't present in the passage.(assert-exists: ?bottomBar)
will produce an error if a ?bottomBar hook isn't present in the passage.
Rationale:
The macros in Harlowe that remotely affect other hooks or text based on their name or contents, such as (click:), are designed such that they
do not cause an error if no matching hooks or text is found in the passage. This allows them to be thought of as similar to CSS rules for how passage prose
is to be rendered - something like (enchant:?dust, (text-style:'blur'))
states the "rule" that ?dust hooks are to be blurred - rather than as imperative commands
that must be fulfilled there and then. This means that they can be placed in every passage, via "header" or "footer" tagged passages, without errors occurring.
But, this flexibility comes at a cost. In the minority of situations where you need to be certain that a macro is affecting a visible in-passage
structure, you'll often want to test with this macro, so as to produce an error if those structures do not exist.
Details:
This command (and note that (assert:) doesn't produce a command) probes the current passage in which it's located in order to determine whether to produce an error or not. As such, like all commands, it can be saved into a variable, and deployed into passage code using that variable, to save having to retype it in full.
If you provide an empty string to this macro (which obviously can't be found in the passage), it will produce a different kind of error than what would be desired.
See also:
The (debug: ) macro
(debug: ) → Command
This command, which takes no values, opens the Debug Mode panel if it wasn't open already. This can be used even if the story didn't begin in Debug Mode initially.
Example usage:
(link:"DEBUG")[(debug:)]
creates a link that opens the Debug Mode panel.(after-error: )[(debug:)]
causes Debug Mode to open if any error occurs in the current passage. This is best used in a "header" or "footer" tagged passage.
Rationale:
Designed for use in testing, this macro allows you to selectively open the Debug Mode panel at certain parts of the game, after having played through the game normally without its presence. It's designed especially for use with (after-error:) - combining them as in the example above will allow you to start debugging immediately upon any error occurring.
Details:
Note that, to ensure that Harlowe runs at optimal performance, some Debug Mode features will not be immediately available if the panel is opened mid-game. In particular, the "replay" dialogs (available in Debug View) won't be available for macros and variables that have already been rendered prior to the panel opening.
Using this macro will not cause the contents of passages tagged with "debug-header" or "debug-footer" to suddenly be added to the current passage. However, if the Debug Mode panel remains open after changing passages, those will be added to subsequent passages.
See also:
The (mock-turns: ) macro
(mock-turns: Number) → Command
A macro that can only be used in debug mode, this allows you to artificially increase the number that the visits
keyword
evaluates to, without having to play through that many turns yourself.
Example usage:
(mock-turns: 3)
means that from here on,turns
(and its aliasturn
) will produce a number 3 higher than the actual number of visits.
Rationale:
Using the turns
keyword as a way to track the amount of "moves" the player has performed instead of using Boolean variables and (set:)
can produce simpler, more understandable code - it's obvious what (if: turns > 15)
means just by looking at it. But, it comes
with a cost: when testing your story using the "Play from here" feature in the Twine editor, you may want to pretend that you have already
performed a certain number of turns, so as to examine the resulting prose. If you were using variables, you could add a few temporary
(set:) macros to the passage, or put them in a "debug-header" tagged passage, to adjust the variables to match a game in progress. This macro
provides that same functionality to the turns
keyword, letting you temporarily adjust it for testing purposes.
Details:
It's critical to understand that these are mock turns - no additional passages have actually been visited.
As stated above, this macro will cause an error if it's used outside debug mode. This is NOT intended for use in a final game - while temporarily
tweaking the meaning of turns
is convenient for testing, the author should be able to trust that in the "real" game, they
correctly report the turns the player has actually made, so that the story's code can be properly understood.
If this macro is used multiple times, only the final usage will count - all the rest will be forgotten. (mock-turns:7)(mock-turns:0)
, for instance,
will cause turns
to behave normally.
The effects of (mock-turns:) are saved by (save-game:), so you can use save files for testing your story with these effects.
If you undo past a passage that used (mock-turns:), the effects of that macro call will be removed, as if it had been a (set:) macro call.
Giving negative values, or non-whole numbers to this macro will produce an error.
This command can't have changers attached - attempting to do so will produce an error.
See also:
(history:), (set:), (mock-visits:)
The (mock-visits: ) macro
(mock-visits: ...String) → Command
A macro that can only be used in debug mode, this allows you to mark various passages as "visited", even though the player
actually hasn't. This allows you to quickly test passages that use the visits
keyword, or the (history:)
datamap, without
having to play through the whole game from the start.
Example usage:
(mock-visits:"Juice Temple", "Milk Temple", "Water Temple")
marks the passages "Juice Temple", "Milk Temple" and "Water Temple" as each having been visited once.(mock-visits:"Lobby","Lobby","Lobby")
marks the "Lobby" passage as having been visited 3 times.
Rationale:
Using the visits
keyword, or the (history:) array, as a way to track the player's progress instead of using Boolean variables and (set:)
can produce simpler, more understandable code - it's obvious what (if: visits is > 2)
means just by looking at it. But, it comes
with a cost: when testing your story using the "Play from here" feature in the Twine editor, you may want to pretend that you have visited the
requisite passages a given number of times, so as to examine the resulting prose. If you were using variables, you could add a few temporary
(set:) macros to the passage, or put them in a "debug-header" tagged passage, to adjust the variables to match a game in progress. This macro
provides that same functionality to the visits
keyword and (history:) array, letting you temporarily adjust it for testing purposes.
Details:
It's critical to understand that these are mock visits - the passages listed are not actually visited, and code inside them is not run by this macro.
As stated above, this macro will cause an error if it's used outside debug mode. This is NOT intended for use in a final game - while temporarily
tweaking the meaning of visits
and (history:) is convenient for testing, the author should be able to trust that in the "real" game, they
correctly report the visits the player has actually made, so that the story's code can be properly understood.
Each occurrence of a passage name given to this macro counts as a single mock visit. Add multiples of the same passage name to register multiple mock visits to that passage.
If this macro is used multiple times, only the final usage will count - all the rest will be forgotten. (mock-visits:"A")(mock-visits:"B")
, for instance,
will only cause the "B" passage to be considered visited 1 time. This allows you to remove mock visits in the middle of a story by writing (mock-visits:)
.
The effects of (mock-visits:) are saved by (save-game:) as of version 3.2.3.
If you undo past a passage that used (mock-visits:), the effects of that macro call will be removed, as if it had been a (set:) macro call.
This command can't have changers attached - attempting to do so will produce an error.
See also:
(history:), (set:), (mock-turns:)
The (verbatim-source: ) macro
(verbatim-source: Any) → Command
Also known as: (v6m-source:)
A convenient combination of (verbatim-print:) and (source:), this prints out the Harlowe source code of any value given to it.
Example usage:
(v6m-source: (open-storylets: )'s 1st)
prints the source of the first datamap in the array generated by (open-storylets:).
Rationale:
This macro provides a quick way for you to display the source code of a Harlowe value. Normally, you can't easily print the string returned by (source:), because, funnily enough, Harlowe will immediately re-render it. You can use this macro instead. This can be helpful when you're debugging a story and wish to have a complicated expression's value constantly in view, especially in a "debug-footer" tagged passage.
Details:
For more details about the particulars of the source code generated by (source:), see that macro's article. Note that, as with that macro, commands created by custom macros (via the (output:) macro) cannot be converted to source - attempting to do so will produce an error.
See also:
The (history: ) macro
(history: [Lambda]) → Array
This returns an array containing the string names of all of the passages the player has previously visited up to now, in the order that the player visited them. An optional lambda can filter the passages, by checking the (passage:) datamap of each. The (mock-visits:) macro can, during debugging, artifically add values to this array to simulate having visited various passages.
Example usage:
(history: where "Intermission" is not in its name)
is an array of visited passage names, but not including passages whose name contains "Intermission" anywhere in it.(history: where its tags contains "Forest")'s length
is the number of times a passage tagged with "Forest" was visited during this game.(unique: ...(history: where its tags contains "Forest"))'s length
is the same as the above, but excludes duplicate visits to the same passages.(if: (history:)'s 3rdlasttolast is (a:"Cliff", "Leap", "Fly"))
checks if the previous 3 passages visited were "Cliff", "Leap" and "Fly".
Rationale:
This macro provides easy access to the names of passages visited on past turns. Non-linear stories
may result in a lot of possible paths for the player to take, and so, it is often desirable
to look back and see which passages the player visited, and when.
Often, you may find yourself using "flag" variables to keep track of whether
the player has visited a certain passage in the past. In some cases, you can use (history:), along with
data structure operators, such as the contains
operator, to obviate this necessity.
Details:
If you simply wish to check that a particular passage, or set of passages, has been visited even once, you may find the (visited:) macro more suited to your needs.
The array includes duplicate names if the player has visited a passage more than once, or visited the same passage two or more turns in a row.
Passages visited via (redirect:) will be included in this array. Each passage redirected to will appear immediately after the passage that the (redirect:) macro was called in.
This does not include the name of the current passage the player is visiting.
This macro can optionally be given a where
lambda, which is used to only include passage names in the
returned array if they match the lambda. Note that even though this produces an array of strings,
the variable in the lambda (referred to by the it
keyword or by a temp variable
placed before the word where
) is always a datamap -
the same datamap as would be returned by (passage:) for that passage name. That datamap contains
these values:
Name | Value |
---|---|
source | The source markup of the passage, exactly as you entered it in the Twine editor |
name | The string name of this passage. |
tags | An array of strings, which are the tags you gave to this passage. |
storylet | If a (storylet:) call is in the passage, this holds the lambda for that call. Otherwise, it's absent. |
exclusivity | The storylet exclusivity number. Usually only present if an (exclusivity:) call is in the passage. |
urgency | The storylet urgency number. Usually only present if an (urgency:) call is in the passage. |
So, you can think of (history: where its tags contains "Forest")
as a shorthand for
(find: where (passage: it)'s tags contains "Forest", ...(history:))
,
which takes the normal (history:) array, and finds only those names for passages whose tags contain "Forest".
If you're testing your story in debug mode using (mock-visits:), then each of the "mock" visits you simulate
using that macro will be added to the front of the returned array (if they match the passed-in lambda).
(mock-visits:"A","B")
will cause (history:)
to produce an array starting with "A","B"
, followed by
passages the tester has actually visited on this playthrough. It will also cause (history: where its name contains "A")
to produce an array starting with "A"
.
By default, Harlowe records an unlimited amount of passage visits. However, you can use the (forget-visits:) macro to make Harlowe "forget" visits that are a certain number of turns old.
See also:
(visited:), (passage:), (mock-visits:), (forget-visits:)
The (visited: ) macro
(visited: String or Lambda) → Boolean
When given a string, this macro produces true if the passage has ever been visited during this game, and false otherwise. When given a "where" lambda, this returns true if any passage matching the lambda has ever been visited during this game. The (mock-visits:) macro can, during debugging, make this macro return true in cases where it would otherwise be false.
Example usage:
(visited:"Cellar")
is true if the player has visited a passage called "Cellar" at some point.(visited: where its tags contains "Forest")
is false if the player visited no passages with the "Forest" tag.
Rationale:
Often, you may find yourself using "flag" variables simply to keep track of whether the player has visited a certain passage in the past. In most such cases, you can use (visited:) instead of having to use these variables.
Details:
If a string name is given, and no passages of that name are in the story, an error will be produced.
Passages visited via (redirect:) will be considered "visited" by this macro, just as they are considered
"visited" by the visits
keyword.
When given a where
lambda, the variable in the lambda (referred to by the it
keyword or by a temp variable
placed before the word where
) is always a datamap - the same datamap as would be returned by (passage:) for that passage name. That datamap contains
these values:
Name | Value |
---|---|
source | The source markup of the passage, exactly as you entered it in the Twine editor |
name | The string name of this passage. |
tags | An array of strings, which are the tags you gave to this passage. |
storylet | If a (storylet:) call is in the passage, this holds the lambda for that call. Otherwise, it's absent. |
exclusivity | The storylet exclusivity number. Usually only present if an (exclusivity:) call is in the passage. |
urgency | The storylet urgency number. Usually only present if an (urgency:) call is in the passage. |
So, you can think of (visited: where its tags contains "Forest")
as a shorthand for
(some-pass:where (passage: it)'s tags contains "Forest", ...(history: ))
.
If you're testing your story in debug mode using (mock-visits:), then any "mock" visit you simulate will be counted as a visit.
By default, Harlowe records an unlimited amount of passage visits. However, you can use the (forget-visits:) macro to make Harlowe "forget" visits that are a certain number of turns old. This is the only way to directly alter this macro's output.
See also:
(history:), (passage:), (mock-visits:), (forget-visits:)
The (passage: ) macro
(passage: [String]) → Datamap
When given a passage string name, this provides a datamap containing information about that passage. If no name was provided, then it provides information about the current passage.
Example usage:
(passage:"Cellar")
Rationale:
There are times when you wish to examine the data of the story as it is running - for instance, checking what
tag a certain passage has, and performing some special behaviour as a result. In particular, checking what data the
current passage has can be very useful in a header
tagged passage, or in a (display:)ed passage. This macro, as
well as its counterpart (passages:), provides that functionality.
Details:
The datamap contains the following names and values.
Name | Value |
---|---|
source | The source markup of the passage, exactly as you entered it in the Twine editor |
name | The string name of this passage. |
tags | An array of strings, which are the tags you gave to this passage. |
storylet | If a (storylet:) call is in the passage, this holds the lambda for that call. Otherwise, it's absent. |
exclusivity | The storylet exclusivity number. Usually only present if an (exclusivity:) call is in the passage. |
urgency | The storylet urgency number. Usually only present if an (urgency:) call is in the passage. |
However, if the passage contained a (metadata:) macro call, then the data provided to that macro call will be added to that passage's datamap. So, you can have any amount of author-defined data in it as well.
The "source" value, like all strings, can be printed using (print:). Be warned that printing the source of the current passage, while inside of it, may lead to an infinite regress.
Interestingly, the construction (print: (passage: "Cellar")'s source)
is essentially identical in function (albeit longer to write)
to (display: "Cellar")
.
See also:
(history:), (passages:), (metadata:)
The (passages: ) macro
(passages: [Lambda]) → Array
This returns an array containing datamaps of information for the passages in the story, sorted by passage name, and using the optional search test to only include certain types of passages.
Example usage:
(passages: where its name contains "Fight")
produces an array of datamaps for passages in the story that
contain "Fight" in their name.
Rationale:
There are times when you wish to examine the data of the story as it is running - for instance, checking which of the story's passages has a certain tag, or a certain word in its source. While you could manually write an array of such passages' data yourself and include them as an array, it is usually easier to use this macro (or the (passage:) macro) to produce such an array automatically.
Details:
The datamaps for each passage resemble those returned by (passage:). They initially contain the following names and values.
Name | Value |
---|---|
name | The string name of the passage. |
source | The source markup of the passage, exactly as you entered it in the Twine editor |
tags | An array of strings, which are the tags you gave to the passage. |
storylet | If a (storylet:) call is in the passage, this holds the lambda for that call. Otherwise, it's absent. |
exclusivity | The storylet exclusivity number. Usually only present if an (exclusivity:) call is in the passage. |
urgency | The storylet urgency number. Usually only present if an (urgency:) call is in the passage. |
However, if the passage contained a (metadata:) macro call, then the data provided to that macro call will be added to that passage's datamap. So, you can have any amount of author-defined data in it as well.
If no passage matches the lambda, then the array will be empty.
If you wish to take the array of passages and reduce them to just their names, the (altered:) macro can be
used. For instance, (altered: via its name, ...(passages:))
produces an array of every passage's name.
See also:
(history:), (passage:), (metadata:)
The (forget-visits: ) macro
(forget-visits: Number) → Command
This macro "forgets" all visits that occurred before the given turn number - any passage visits that occurred on those turns are treated as if they didn't happen.
This causes (history:) to no longer list those passages at the start of the array, the visits
identifier to report a different number for those passages, and
(if the only visit to a passage was erased) the (visited:) macro to no longer regard a passage as visited.
Example usage:
(forget-visits: -10)
causes all visits more than 10 turns old (that is, the 10thlast turn) to expire.(forget-visits: -1)
forgets all visits prior to this turn.
Rationale:
This macro forms a pair with (forget-undos:). These together allow you to erase two kinds of non-variable data tracked by Harlowe - the undo cache, and the record of passage visits.
The visits
identifier and the (history:) macro are not intended to be "special variables" that the author can permute as they may wish - they have a very specific meaning
within Harlowe's language. Using them as they are designed (as permanent records of when the player visited a passage or passages) is highly recommended. This is why
this macro only offers limited functionality for erasing visits.
That being said, there are a few edge-case situations when you'd want to use (forget-visits:). If your story consists entirely of "sub-stories" (like an anthology) that individually use visits
,
but which also need to be revisitable/replayable without restarting the whole story with (restart:), then this (along with (forget-undos:)) can be used to erase all of the visits
once the player has finished a sub-story, so that (along with resetting its variables using (set:)) it can be replayed as it was.
The other situation is when you're writing an "endless" story, one that will be played over an indefinite number of turns, such as a kiosk or an art installation. Because
Harlowe is constantly recording visited passages (as part of the (history:) macro's functionality) then over many thousands of turns, the ever-increasing memory usage of the story may become
detrimental to the browser's performance. Periodically running both (forget-undos:) and (forget-visits:) (in a "header" or "footer" tagged passage, for instance) to erase data from
hundreds of turns ago (such as by (forget-visits:-200)(forget-undos:-200)
will keep the record of visited passages (and the undo cache) from growing beyond a certain point,
preventing this situation from occurring.
Details:
You currently cannot use this macro to erase "mock visits" created with (mock-visits:).
The effects of (forget-visits:) can be undone by the player using (link-undo:), the sidebar undo button, and so forth, so previous turns are unaffected by this command. This also means that the visits still "exist" as long as you can undo past the turn in which (forget-visits:) was used.
If there aren't enough past turns' visits to erase (for instance, when (forget-visits:4)
is run when only two turns have been taken) then
all past visits will be erased.
If 0 is given, no past turns will be erased.
Because of the very specific purposes this macro is designed for, there is no way to forget visits of just a single passage, or after a certain turn, or in other, more specific ways. If you really need to manipulate a passage's recorded visits like this, please use a variable to track "visits" to this passage instead.
See also:
The (forget-undos: ) macro
(forget-undos: Number) → Command
This macro, when used, "forgets" previous turns, preventing the player from using undo features (like (link-undo:)) to return to them. Providing a positive number will forget that many turns from the start of the game, and providing a negative number will forget all the turns up to that point from the end.
Example usage:
(forget-undos:-2)
forgets all previous turns up to and including the second-last turn.(forget-undos:-1)
forgets all previous turns.(forget-undos:1)
forgets the first turn. This could be useful for erasing a "title screen" turn from the game session, which doesn't make sense for the player to "undo".
Rationale:
By default, Harlowe allows the player (via the default sidebar) to undo any number of turns, all the way up to the start of the game. This macro makes it easy to limit this ability, either at key moments in the story (such as the starts of chapters or viewpoint switches) or constantly, by placing it in a "header" or "footer" tagged passage.
Note that while it's possible to limit the player's ability to undo by simply removing or replacing the default (icon-undo:)
instance from the sidebar (such as, for example, (replace:?sidebar)[(if:$canUndo)(icon-undo: )]
, and then conditionally changing the $canUndo variable
throughout the story), this macro provides a more direct way of limiting undos. In particular, it makes it
easy to limit the distance that a player can undo from the current turn.
It is recommended that you be careful in your use of this macro. In most cases, limiting the player's ability to undo actions isn't a particularly interesting or clever game design restriction. However, there are certain genres of game, such as survival horror games or experiential dream simulators, where suddenly limiting the ability to undo at periodic intervals can have a desirable disempowering effect.
There is one other situation where (forget-undos:) may be of use: when you're writing an "endless" story, one that will be played over an indefinite
number of turns, such as a kiosk or an art installation. Because Harlowe constantly maintains an undo cache, then over many thousands of turns,
the ever-increasing memory usage of the story may become detrimental to the browser's performance. Periodically using (forget-undos:) and (forget-visits:)
(in a "header" or "footer" tagged passage, for instance) to erase data from hundreds of turns ago (such as by (forget-visits:-200)(forget-undos:-200)
will keep the undo cache (and the record of visited passages) from growing beyond a certain point, preventing this situation from occurring.
Details:
This macro does nothing if it is run in a passage that has been returned to by "undo" (i.e. where there is one or more turns one could "redo" using (icon-redo:)). It only functions in the "present".
If there aren't enough past turns to forget (for instance, when (forget-undos:4)
is run when only two turns have been taken) then
all past turns will be forgotten.
There is no way to "un-erase" the forgotten turns when this macro is used. Its effects are permanent.
Use of this macro will not affect the (history:) macro, the (visited:) macro, or the visits
and turns
keywords (nor
the debug-only (mock-visits:) and (mock-turns:) macros). These will continue to perform as if (forget-undos:) was never called.
However, the turns that they refer to (in which the "visits" occurred) will no longer be accessible to the player. If you wish
to affect the (history:) macro and the like, consider using (forget-visits:).
This macro will internally "flatten" every forgotten turn into a single data structure, which is used to ensure that (history:) and (visited:) and other such features continue to behave as expected. As a result, using this macro will reduce the size of the data saved to browser localStorage by (save-game:). However, you should not use this macro solely on the hunch that it provides performance benefits for your story - such benefits only come to stories that take place over a very large number of turns, and who store a large amount of data in global variables (such as expansive datamaps and arrays that are changed frequently).
If 0 is given, no past turns will be forgotten.
See also:
The (metadata: ) macro
(metadata: [...Any]) → Metadata
When placed in a passage, this adds the given names and values to the (passage:) datamap for this passage.
Example usage:
( "danger", 4, "hint", "Dragon teeth are fire-hardened.")
in a passage named "Dragon dentistry" causes(passage:"Dragon dentistry")'s danger
to be 4, and(passage:"Dragon dentistry")'s hint
to equal the given string.( "rarity", 5)
in a passage called "Adamantium" causes(passage: "Adamantium")'s rarity
to be 5. You can then use(find: where its rarity >= (random: 1, 10), ...(passages: where it contains "rarity"))
to get a list of passages that may randomly exclude the "Adamantium" passage.
Rationale:
While the (passage:) and (passages:) datamaps can provide the tags, name and source code of your story's passages by default, there are many cases when you need more specific data than just strings, such as a number or a changer. An example is when making links for passages that have been chosen non-deterministically, such as by (open-storylets:) - you may want the link to be accompanied with a short description fitting the passage, or you may want each passage to have a random chance of not appearing at all. Moreover, you want to be able to write this information inside the passage itself, as you write it, just as tags are written on the passage as you write it.
The (metadata:) macro provides this functionality - it augments the (passage:) datamap for the current passage, adding extra data of your choosing to it, as if by adding a (dm:) to it at startup.
Details:
The data names and values are provided to (metadata:) as if it was a (dm:) macro call – first the string names, then the values, in alternation.
Being a metadata macro, a (metadata:) macro call must appear in the passage before every other non-metadata macro in the passage, such as (set:) and (if:). (This doesn't include macros inside a "header" or "footer" tagged passage.) The recommended place to put it is at the top of the passage.
Every passage's (metadata:) macro is run just once, at startup. If an error occurs while doing so (for instance, if a value is given without a matching name) then a dialog box will appear at startup, displaying the error.
Since passages already have "source", "name" and "tags" data names in their datamap, trying to use these names in a (metadata:) macro will produce an error.
Putting this in a "header", "startup" or "footer" tagged passage will NOT cause this metadata to be applied to every passage, much as how adding extra tags to a "header", "startup" or "footer" tagged passage will not cause those tags to apply to every passage.
See also:
(passage:), (passages:), (storylet:)
The (seed: ) macro
(seed: String) → Command
A command that "fixes" Harlowe's random number generator, causing all random macros and features to output predetermined values based on the given "seed" string.
Example usage:
(seed:"aeiouy")(random:1,10) (random:1,10) (random:1,10)
will print 3 2 6
.
Rationale:
One of the appeals of randomness in games is its unpredictability, but one of the detriments of it is its irreplicability. Once a series of random values has been rolled, it's usually not possible to replicate that streak of luck. This can be bothersome if, for instance, you want to give an unpredictable challenge to players, but don't want any one player to arbitrarily get an easier or harder challenge than another.
This macro gives you the ability to control the randomness of your story. Harlowe (like most computer games) uses a "pseudo" random number generator (pseudo RNG, or PRNG) to make random choices. (It is "pseudo" because truly random numbers cannot be generated by a computer, and can only be found by sampling external physical phenomena.)
Think of the PRNG as a "vine" of numbers, that "grow" from a single "seed" (a special modifier value). Each random feature in your story (like a single (either:) call) takes a number from the sequence and uses it to make a choice (like picking one of the (either:) values). So, as long as the seed is the same, the same values will be "grown", and if the player encounters random features in the same order, the same values will be taken.
You may want to use (seed:) for these purposes:
- Seed it based on a player name (inputted via (input-box:) or (prompt:)) to assign a unique challenge to each player that can
- Seed it based on the current date (using (current-date:)) to produce a "daily challenge" that will be the same for all players (provided their system clock is set to the current date).
Details:
The oft-mentioned "random features" are the (random:), (either:) and (shuffled:) macros, as well as the random
data names (invoked using 's random
or random of
).
Even though I've called the seed a "special modifier value", this macro actually takes a string. This is so that the aforementioned uses (such as using (current-date:)) are simple, and also so that you may provide (seed:) with easy-to-remember words like "apple", if you so wish. The string is internally converted to a number using a complicated procedure that ensures similar strings (like "app" and "appl") still grow very different sequences of random numbers. So, don't be concerned with using too-similar strings as seed values.
Harlowe's random number generator is initially, automatically seeded based on the timestamp at which the game begins so you do not need to call this at all to get unpredictable randomness that isn't replicated across playthroughs. You should use this only to control your story's randomness, not create it. (restart:) will reset the seed using this method.
The current seed (and the number of values currently taken) is saved when you use (save-game:), so loading a saved game will continue to use the same sequence of random values that were in use when the game was saved.
(seed:) is best used at the very beginning of the story, or at least before the first random feature is used. Using it in the middle of the story will cause the randomness to use a different seed, and should generally only be used when you wish the player to replay a section of the story with different random values.
Giving the same string to (seed:) will reset the number of values taken. So, if (seed:"A")
followed by five (random:1,10)
calls produced 4,2,8,4 and 9 in that order, then calling (seed:"A")
again will cause the next five (random:1,10)
calls to produce 4,2,8,4 and 9 in that order.
Using (seed:) multiple times will not affect the "randomness" of the numbers produced - they will be similarly "random" and unpredictable.
For reference, Harlowe's random number generator is mulberry32 by Tommy Ettinger, and the seed is hashed with MurmurHash3 by Austin Appleby. It is not expected that Harlowe stories will use random elements at a frequency expected of action games (which use RNG up to hundreds of times per frame), and therefore a 32-bit algorithm should be sufficient.
See also:
(random:), (either:), (shuffled:)
The (cycling-link: ) macro
(cycling-link: [Bind], ...String) → Command
A command that, when evaluated, creates a cycling link - a link which does not go anywhere, but changes its own text to the next in a looping sequence of strings, and sets the optional bound variable to match the string value of the text.
Example usage:
(cycling-link: bind $head's hair, "Black", "Brown", "Blonde", "Red", "White")
binds the "hair" value in the $head datamap to the current link text.(cycling-link: "Mew", "Miao", "Mrr", "Mlem")
has no bound variable.(cycling-link: 2bind $pressure, "Low", "Medium", "High")
has a two-way bound variable. Whenever $pressure is either "Low", "Medium", or "High", the link will change its text automatically to match.
Rationale:
The cycling link is an interaction idiom popularised in Twine 1 which combines the utility of a dial input element with the discovery and visual consistency of a link: the player can typically only discover that this is a cycling link by clicking it, and can then discover the full set of labels by clicking through them. This allows a variety of subtle dramatic and humourous possibilities, and moreover allows the link to sit comfortably among passage prose without standing out as an interface element.
The addition of a variable bound to the link, changing to match whichever text the player finally dialed the link to, allows cycling links to affect subsequent passages, and thus for the link to be just as meaningful in affecting the story's course as any other, even though no hooks and (set:)s can be attached to them.
Details:
This macro accepts two-way binds using the 2bind
syntax. These will cause the link to rotate its values to match the current value of the bound
variable, if it can - if $pressure is "Medium" when entering the passage with (cycling-link: 2bind $pressure, "Low", "Medium", "High")
, then it will
rotate "Medium" to the front, as if the link had already been clicked once. Also, it will automatically update itself whenever
any other macro changes the bound variable. However, if the variable no longer matches any of the link's strings, then it won't update - for
instance, if the variable becomes "It's Gonna Blow", then a cycling link with the strings "Low", "Medium" and "High" won't update.
If one of the strings is empty, such as (cycling-link: "Two eggs", "One egg", "")
, then upon reaching the empty string, the link
will disappear permanently. If the first string is empty, an error will be produced (because then the link can never appear at all).
If attempting to render one string produces an error, such as (cycling-link: "Goose", "(print: 2 + 'foo')")
, then the error will only appear
once the link cycles to that string.
The bound variable will be set to the first value as soon as the cycling link is displayed - so, even if the player doesn't interact with the link at all, the variable will still have the intended value.
If the bound variable has already been given a type restriction (such as by (set:num-type $candy to 1)
), then, if that type isn't string
or str
, an error
will result.
If you use (replace:) to alter the text inside a (cycling-link:), such as (cycling-link: bind $tattoo, "Star", "Feather")(replace:"Star")[Claw]
,
then the link's text will be changed, but the value assigned to the bound variable will not - $tattoo will still be "Star", and clicking the
link twice will return the link's text to "Star". This differs from (dropdown:)'s behaviour in this situation.
If only one string was given to this macro, an error will be produced.
The (seq-link: ) macro
(seq-link: [Bind], ...String) → Command
Also known as: (sequence-link:)
A command that creates a link that does not go anywhere, but changes its own text to the next in a sequence of strings, becoming plain text once the final string is reached, and setting the optional bound variable to match the text at all times.
Example usage:
(seq-link: bind $candy, "Two candies", "One candy", "Some wrappers")
sets the $candy variable to always equal the currently displayed string. "Some wrappers", the final string, becomes plain text instead of a link.(seq-link: "We nodded,", "turned,", "and departed, not a word spoken")
has no bound variable.
Rationale:
This is a variation of the (cycling-link:) command macro that does not cycle - for more information about that macro, see its corresponding article. This is a simpler macro, being simply a link that changes when clicked without looping, albeit less useful as a means of obtaining the player's input.
While it's possible to produce this effect by simply using (link:) and nesting it, such as by (link:"We nodded,")[(link:"turned,")[and departed, not a word spoken]]
,
this macro is much more convenient to write when you wish to use a large amount of link labels. Additionally, this macro allows a bound variable to
keep track of which string the player viewed last, as with (cycling-link:), which would be slightly more complicated to track using (link:) and (set:).
Details:
If one of the strings is empty, such as (seq-link: "Two eggs", "One egg", "")
, then upon reaching the empty string, the link
will disappear permanently. If the first string is empty, an error will be produced (because then the link can never appear at all).
If attempting to render one string produces an error, such as (seq-link: "Goose", "(print: 2 + 'foo')")
, then the error will only appear
once the link cycles to that string.
The bound variable will be set to the first value as soon as the sequence link is displayed - so, even if the player doesn't interact with the link at all, the variable will still have the intended value.
If the bound variable has already been given a type restriction (such as by (set:num-type $candy to 1)
), then, if that type isn't string
or str
, an error
will result.
If you use (replace:) to alter the text inside a (seq-link:), such as (seq-link: bind $candy, "Two candies", "One candy", "Some wrappers")(replace:"Two")[Five]
,
then the link's text will be changed, but the value assigned to the bound variable will not - $candy will still be "Two candies" until the link is clicked.
If only one string was given to this macro, an error will be produced.
The (input: ) macro
(input: [Bind], [String], [String]) → Command
A command macro that creates a single-line text input element, allowing the player to input any amount of text without newlines, which can optionally be automatically stored in a variable. The first string specifies the horizontal position and width, and the second string specifies an initial default value to fill the element with.
Example usage:
(input: "Cheese Honey Sandwich")
produces an element that initially contains "Cheese Honey Sandwich", and which is 100% of the available width. Altering the contained text does nothing.(input: bind _name, "=X=", "Calder Faust")
produces an element that initially contains "Calder Faust", and which is 33% of the available width. Altering it automatically updates the _name temp variable.(input: bind _spell)
produces an element which is 100% of the available width, and with no initial default text inside it. Altering it automatically updates the _spell temp variable.
Rationale:
While there are other means of accepting player text input into the story, such as the (prompt:) macro, you may desire an input region that is integrated more naturally into the passage's visual design, and which allows a greater quantity of text to be inputted. This macro offers that functionality, and provides an easy means for that inputted text to be stored in a variable.
Details:
This macro has no mandatory values - (input:)
by itself will produce a text input element with no bound variable, no placeholder and no default value.
The optional sizing string is the same kind of line given to (box:) - a sequence of zero or more =
signs, then a sequence of characters (preferably "X"), then zero or
more =
signs. Think of this string as a visual depiction of the element's horizontal proportions - the =
signs are the space to
the left and right, and the characters in the middle are the element itself. Also, to avoid ambiguity with the second string given to this macro,
a string representing 100% width (no margins) must be a single character, such as just "X". If you need the initial contents of the element to be a single character,
provide an "X" sizing string before it, so that it's clear which is which.
The produced element always occupies an entire line of the containing area, as with (box:) and (button:). If you wish to place it alongside other text, consider using it inside the column markup.
This macro accepts two-way binds using the 2bind
syntax. These will cause the element's contents to always match the current value of the bound
variable, and automatically update itself whenever any other macro changes it. However, if the variable no longer contains a string, then
it won't update - for instance, if the variable becomes the number 23, the element won't update.
If the bound variable isn't two-way, the variable will be set to the element's contents as soon as it is displayed - so, it will become the optional initial text string, or, if it wasn't given, an empty string.
If the bound variable has already been given a type restriction (such as by (set:num-type $candy to 1)
), then, if that type isn't string
or
str
, an error will result.
The optional initial text string given to this macro will not be parsed as markup, but inserted into the element verbatim - so, giving
"''CURRENT SAVINGS'': $lifeSavings"
will not cause the $lifeSavings variable's contents to be printed into the element, nor will "CURRENT SAVINGS"
be in boldface.
A note about player-submitted strings: because most string-printing functionality in Harlowe (the (print:) macro, and putting variable names in bare passage prose) will attempt to render markup inside the strings, a player may cause disaster for your story by placing Harlowe markup inside an (input:) bound variable, which, when displayed, produces either an error or some effect that undermines the story. In order to display those strings safely, you may use either the verbatim markup, the (verbatim:) changer, or (verbatim-print:).
As of 3.3.2, Harlowe will attempt to auto-focus input elements when they are added to the passage, allowing the player to type into them immediately. If multiple input elements are present, the first (highest) one will be auto-focused. Note that any further input elements added to the passage (via (after:) or some other means) will be auto-focused even if the player is currently typing into an existing element.
See also:
(input-box:), (force-input:), (prompt:)
The (force-input: ) macro
(force-input: [Bind], [String], String) → Command
A command macro that creates a single-line text input element which appears to offer the player a means to input text, but instead replaces every keypress inside it with characters from a pre-set string that's relevant to the story.
Example usage:
(force-input: bind _cmd, "ERASE INTERNET")
creates an input element which forces the player to type the string "ERASE INTERNET", and binds the current contents of the element to the _cmd temp variable. If the player types four characters into it, _cmd will be the string "ERAS".
Rationale:
There are times when, for narrative reasons, you want the player, in the role of a character, to type text into a diegetic textbox, or make a seemingly "spontaneous" dialogue reply, but are unable to actually permit the player to type anything they want, as the story you're telling calls for specific dialogue or text at this point. While you could simply offer a "pretend" textbox using the (box:) macro, that can't actually be typed into, this macro offers an interesting and unexpected alternative: a text element that tricks the player into thinking they can type anything, only to change the text to fit your narrative letter-by-letter as they type it.
This interface element has very potent and unsettling symbolism - the player suddenly being unable to trust their own keyboard to relay their words gives a strong feeling of unreality and loss of control, and as such, it is advised that, unless you wish to leverage that symbolism for horror purposes, you should perhaps prepare the player for this element's eccentricity with some accompanying text. Besides that, giving the player the tactile sense of typing words can help them occupy the role of their viewpoint character in situations where it's called for, such as a story revolving around text messaging or chat clients.
Details:
Unlike (input:), the final string is mandatory, as it holds the text that the input element will contain as the player "types" it in.
The optional sizing string is the same kind of line given to (box:) - a sequence of zero or more =
signs, then a sequence of characters (preferably "X"), then zero or
more =
signs. Think of this string as a visual depiction of the box's horizontal proportions - the =
signs are the space to
the left and right, and the characters in the middle are the box itself. Also, to avoid ambiguity with the second string given to this macro,
a string representing 100% width (no margins) must be a single character, such as just "X". If you need the initial contents of the element to be a single character,
provide an "X" sizing string before it, so that it's clear which is which.
The produced element always occupies an entire line of the containing area, as with (box:) and (button:). If you wish to place it alongside other text, consider using it inside the column markup.
Because you already know what the text in the element will become, you may feel there's no need to have a bound variable. However, you might wish to bind a temporary variable, and then check using a live macro when that variable has become filled with the full string, thus indicating that the player has read it. Otherwise, there is no mechanism to ensure that the player actually type out the entire string.
If the bound variable is two-way, and it contains a string, then, when the input box appears, a number of fixed text characters equal to the string's length will be inserted into the input element automatically, and then the variable will update to match. Otherwise, if the bound variable is one-way, the variable will simply become an empty string (and then be updated to match the element's contents whenever the player "types" into it).
As of 3.3.2, Harlowe will attempt to auto-focus input elements when they are added to the passage, allowing the player to type into them immediately. If multiple input elements are present, the first (highest) one will be auto-focused. Note that any further input elements added to the passage (via (after:) or some other means) will be auto-focused even if the player is currently typing into an existing element.
See also:
(input:), (force-input-box:), (prompt:)
The (input-box: ) macro
(input-box: [Bind], [String], [Number], [String]) → Command
A command macro that creates a multi-line text input box of the given position, width (specified by the first, optional string) and height (specified by the optional number), allowing the player to input any amount of text, which can optionally be automatically stored in a variable. The final optional string specifies an initial default value to fill the box with.
Example usage:
(input-box: "=X=")
creates an input box that's 33% of the passage width, centered, and 3 lines tall.(input-box: "XXX=", 5)
creates an input box that's 75% of the passage width, positioned left, and 5 lines tall.(input-box: bind $code, "XXX=", 5)
creates an input box that's the same as above, but whenever it's edited, the text is stored in the $code variable.(input-box: bind $code, "XXX=", 5, "10 PRINT 'HELLO'")
creates an input box that's the same as above, but initially contains the text"10 PRINT 'HELLO'"
.
Rationale:
This macro forms a pair with (input:). This is a variation of that macro which allows multi-line text to be inputted. While the former macro is best used for obtaining short, informative strings from the player, this macro can be used to obtain entire sentences or paragraphs, which may be desirable if the story is themed around writing.
Details:
The optional sizing string is the same kind of line given to (box:) - a sequence of zero or more =
signs, then a sequence of characters (preferably "X"), then zero or
more =
signs. Think of this string as a visual depiction of the box's horizontal proportions - the =
signs are the space to
the left and right, and the characters in the middle are the box itself. Also, to avoid ambiguity with the second string given to this macro,
a string representing 100% width (no margins) must be a single character, such as just "X". If you need the initial contents of the element to be a single character,
provide an "X" sizing string before it, so that it's clear which is which.
The produced element always occupies an entire line of the containing area, as with (box:) and (button:). If you wish to place it alongside other text, consider using it inside the column markup.
The optional number, which must come directly after the sizing line, is a height, in text lines. If this is absent, the box will be sized to 3
lines. Harlowe's default CSS applies resize:none
to the box, preventing it (in most browsers) from being resizable by the player.
This macro accepts two-way binds using the 2bind
syntax. These will cause the box's contents to always match the current value of the bound
variable, and automatically update itself whenever any other macro changes it. However, if the variable no longer contains a string, then
it won't update - for instance, if the variable becomes the number 23, the box won't update.
If the bound variable isn't two-way, the variable will be set to the box's contents as soon as it is displayed - so, it will become the optional initial text string, or, if it wasn't given, an empty string.
If the bound variable has already been given a type restriction (such as by (set:num-type $candy to 1)
), then, if that type isn't string
or
str
, an error will result.
The optional initial text string given to this macro will not be parsed as markup, but inserted into the box verbatim - so, giving
"''CURRENT SAVINGS'': $lifeSavings"
will not cause the $lifeSavings variable's contents to be printed into the box, nor will "CURRENT SAVINGS"
be in boldface.
A note about player-submitted strings: because most string-printing functionality in Harlowe (the (print:) macro, and putting variable names in bare passage prose) will attempt to render markup inside the strings, a player may cause disaster for your story by placing Harlowe markup inside an (input-box:) bound variable, which, when displayed, produces either an error or some effect that undermines the story. In order to display those strings safely, you may use either the verbatim markup, the (verbatim:) changer, or (verbatim-print:).
As of 3.3.2, Harlowe will attempt to auto-focus input elements when they are added to the passage, allowing the player to type into them immediately. If multiple input elements are present, the first (highest) one will be auto-focused. Note that any further input elements added to the passage (via (after:) or some other means) will be auto-focused even if the player is currently typing into an existing element.
See also:
(force-input-box:), (input:), (prompt:)
The (force-input-box: ) macro
(force-input-box: [Bind], [String], [Number], String) → Command
A command macro that creates an empty text input box of the given position, width (specified by the first, optional string) and height (specified by the optional number), which appears to offer the player a means to input text, but instead replaces every keypress inside it with characters from a pre-set string that's relevant to the story.
Example usage:
(force-input-box: "XX=", "I'm sorry, father. I've failed you.")
creates an input box that's 33% of the passage width, centered, and which forces the player to type the string "I'm sorry, father. I've failed you.".
Rationale:
this macro forms a pair with (force-input:). For a full elaboration on the purposes of a 'forced' input element as an interactive storytelling device, see the article for (force-input:). This serves to provide a multi-line textbox, compared to the former's single-line element, allowing longer runs of text to appear in it.
Details:
Unlike (input-box:), the final string is mandatory, as it holds the text that the input box will contain as the player "types" it in.
The first string you give to this macro is a "sizing line" identical to that accepted by (box:) and (input-box:) - consult their documentation for more information about those lines.
The optional number, which must come directly after the sizing line, is a height, in text lines. If this is absent, the box will be sized to 3
lines. Harlowe's default CSS applies resize:none
to the box, preventing it (in most browsers) from being resizable by the player.
Because you already know what the text in the box will become, you may feel there's no need to have a bound variable. However, you might wish to bind a temporary variable, and then check using a live macro when that variable has become filled with the full string, thus indicating that the player has read it. Otherwise, there is no mechanism to ensure that the player actually type out the entire string.
If the bound variable is two-way, and it contains a string, then, when the input box appears, a number of fixed text characters equal to the string's length will be inserted into the input box automatically, and then the variable will update to match. Otherwise, if the bound variable is one-way, the variable will simply become an empty string (and then be updated to match the box's contents whenever the player "types" into it).
As of 3.3.2, Harlowe will attempt to auto-focus input elements when they are added to the passage, allowing the player to type into them immediately. If multiple input elements are present, the first (highest) one will be auto-focused. Note that any further input elements added to the passage (via (after:) or some other means) will be auto-focused even if the player is currently typing into an existing element.
See also:
(input-box:), (force-input:), (prompt:)
The (checkbox: ) macro
(checkbox: Bind, String) → Command
A command that creates a checkbox input, which sets the given bound variable to true
or false
, depending on its state.
Example usage:
(checkbox: bind $gore, "Show violent scenes")
creates a checkbox labeled "Show violent scenes" which is initially unchecked.(checkbox: 2bind $perma, "Permadeath")
creates a checkbox which is initially checked if $perma istrue
, and continues to update itself whenever some other macros change $perma.
Rationale:
This command uses the common web page checkbox input to let you ask the player for their preference on an auxiliary or metatextual feature or decision. Unlike the (confirm:) command, this doesn't directly ask the player a yes/no question, but simply presents a phrase or option that they can opt into or out of. Thus, it is useful for offering choices that aren't directly "in-character" or diegetic in the narrative - though, you could still use it for that purpose if the clinical nature of a checkbox was especially fitting for the setting.
Details:
This macro accepts two-way binds using the 2bind
syntax. These will cause the checkbox's contents to always match the current value of the bound
variable, and automatically update itself whenever any other macro changes it. However, if the variable no longer contains a boolean, then
it won't update - for instance, if the variable becomes the number 23, the checkbox won't update.
If the bound variable isn't two-way, the checkbox will be unchecked when it appears, and the variable will be set to false
as soon as it is displayed.
If the bound variable has already been given a type restriction (such as by (set:num-type $candy to 1)
), then, if that type isn't string
or
str
, an error will result.
If the label string is empty, an error will result.
See also:
(dropdown:), (input-box:), (confirm:)
The (checkbox-fullscreen: ) macro
(checkbox-fullscreen: String) → Command
A command that creates a checkbox input, which toggles the browser's fullscreen mode and windowed mode. The checkbox will automatically update to match the browser's fullscreen status. If fullscreen mode cannot be entered, the checkbox will be disabled.
Example usage:
(checkbox-fullscreen: "Fullscreen mode")
Rationale:
Modern browsers allow web pages to take up the entire screen of the user, in a manner similar to desktop games. This feature can be useful for immersive or moody stories, such as horror stories, that wish to immerse the player's senses in a certain colour or shade, or to display impactful text that doesn't have to compete for attention from any other screen elements. While it can be more convenient to place an (icon-fullscreen:) in your story's sidebar, this macro can be useful if you remove or replace the sidebar with something else, and can serve as an alternative means of activating fullscreen mode.
Details:
When activated, this will make the page's <html>
element be the fullscreen element, not <tw-story>
. This is because, in most browsers,
removing the fullscreen element from the page, however briefly, will deactivate fullscreen mode. Since the (enchant:)
macro, when given ?Page,
will often need to wrap <tw-story>
in another element, those macro calls will deactivate fullscreen mode if <tw-story>
was the fullscreen element.
So, if you have edited the compiled HTML to add elements before and after it, such as a navigation bar, that will remain visible while the story
is in fullscreen mode. Additionally, this means that the Debug Mode panel is still visible when fullscreen mode is activated.
Currently, there is no special functionality or error reporting when the browser reports that fullscreen mode is available, but the attempt to switch to fullscreen mode fails. In that case, the checkbox will simply appear to do nothing.
See also:
(checkbox:), (link-fullscreen:), (icon-fullscreen:)
The (dropdown: ) macro
(dropdown: Bind, ...String) → Command
A command that, when evaluated, creates a dropdown menu with the given strings as options. When one option is selected, the bound variable is set to match the string value of the text.
Example usage:
(dropdown: bind _origin, "Abyssal outer reaches", "Gyre's wake", "The planar interstice")
has a normal bound variable.(dropdown: 2bind $title, "Duke", "King", "Emperor")
has a two-way bound variable - if $title is "Duke", "King" or "Emperor", then the dropdown will automatically be scrolled to that option.
Rationale:
Dropdown menus offer a more esoteric, but visually and functionally unique way of presenting the player with a choice from several options. Compared to other list-selection elements like (cycling-link:)s or lists of links, dropdowns are best used for a long selection of options which should be displayed all together, but would not otherwise easily fit in the screen in full.
While dropdowns, whose use in form UI suggests themes of bureaucracy and utility, may appear best used for "character customisation" screens and other non-narrative purposes, that same imagery can also be a good reason to use them within prose itself - for instance, to present an in-story bureaucratic form or machine control panel.
Details:
This macro accepts two-way binds using the 2bind
syntax. These will cause the dropdown to always match the current value of the bound
variable, if it can. Also, it will automatically update itself whenever any other macro changes the bound variable. However,
if the variable no longer matches any of the dropdown's strings, then it won't update - for
instance, if the variable becomes "Peasant", then a dropdown with the strings "Duke", "King" and "Emperor" won't update.
Note that unlike (cycling-link:), another command that uses bound variables, the bound variable is mandatory here.
Also note that unlike (cycling-link:), empty strings can be given. These instead create separator elements, which are rendered as unselectable horizontal lines that separate groups of options. Having empty strings as the first or last elements, however, will result in an error (as these can't meaningfully separate one group from another).
The first element in a (dropdown:) will always be the one initially displayed and selected - and thus, the one that is immediately set into the bound variable.
If you use (replace:) to alter the text inside a (dropdown:), such as (dropdown: bind $tattoo, "Star", "Feather")(replace:"Star")[Claw]
,
then the option text and the value assigned to the bound variable will change - but only when the player next interacts with the dropdown.
$tattoo will be "Star" until a new option is selected, whereupon it will become either "Claw" or "Feather" depending on which was picked.
Harlowe markup inside (dropdown:) labels will be ignored - the text will be treated as if the markup wasn't present. For instance,
(dropdown: bind $mode, "//Stealth//", "//Speed//")
will produce a dropdown with "Stealth" and "Speed", with the italic styling markup removed.
See also:
The (meter: ) macro
(meter: Bind, Number, String, [String], [Colour or Gradient]) → Command
A command that creates a horizontal bar-graph meter, showing the current value of a number variable, relative to a maximum value, and updating it whenever that variable changes.
Example usage:
(set:$batteryPower to 800)(meter: bind $batteryPower, 1000, "X", "Battery Power: $batteryPower", (gradient: 90, 0, red, 1, orange))
creates a centered meter showing the value of the $batteryPower variable, from 0 to 1000, using a gradient from orange (full) to red (empty).(set:$threatLevel to 2)(b4r:'solid')(meter: bind $threatLevel, 10, "==X", red)
creates a right-aligned meter showing the value of the $threatLevel variable, from 0 to 10, in red, with a solid border.
Rationale:
For those making number-heavy games, presenting those numbers in an immediately recognisable fashion can be essential to a smooth game experience - and there are times when simply stating the numbers in the prose isn't as direct as you'd like. The standard videogame UI meter, a bar that fills with a colour to represent an important value, is a visual idiom familiar to many people. In addition to their familiarity, meters have important semantic value, too - simply by graphically presenting a value in a meter, a player can immediately get a sense of how important to their play session that value is, as well as understand what numeric range that value should occupy during play.
Details:
The meter will graph the value of the bound variable, from 0 to the given maximum value number (which must be positive). For instance, if that number is 20, then if the bound variable is 5, the meter bar will be 25% full.
As of Harlowe 3.3.0, using the 2bind
keyword instead of bind
will produce an error.
The meter is a "box" element, which takes up the full width of the passage or hook in which it's contained. Placing it inside column markup can allow you to place text alongside it, or other (meter:) commands, if you so desire.
The first string you give to this macro is a "sizing line" identical to that accepted by (box:) and (input-box:) - consult their documentation for more information about those lines. However, the sizing line also determines the direction that the meter's bar fills. If the meter is left-aligned or occupies the full width (by being given "X" as a sizing string), the bar fills from left (empty) to right (full), and the opposite is true for right-alignment. Centre-alignment causes the bar to fill from the centre, expanding outward in both directions.
The second, optional string is a label that is placed inside the meter, on top of the bar. This text is aligned in the same direction as the meter itself. Whenever the meter updates, the label is also re-rendered.
Either a colour or a gradient can be given as the final value, with which to colour the bar. If neither is given, the bar will be a simple gray. If a gradient is given, and it isn't a (stripes:) gradient, its rotation will be automatically changed to 90 degrees, with the leftmost colour (at colour stop 0) being placed at the "empty" end of the meter, and the rightmost colour (at colour stop 1) placed at the "full" end. If the bar is center-aligned, then the gradient will be modified, with both ends of the graph having the leftmost colour, and the center having the rightmost colour.
The meter is exclusively horizontal, and cannot be rotated unless you attach (text-rotate:) to it.
Note: In Internet Explorer 10, the vertical height of the meter may be lower than as drawn in other browsers. This is due to a CSS limitation in that browser.
See also:
The (link: ) macro
(link: String, [Changer]) → Changer
Also known as: (link-replace:)
When attached to a hook, this replaces the hook with a link that has the given text. The link, when clicked, vanishes and reveals the hook. An optional changer can be given to alter the style of the link (instead of altering the style of the attached hook).
Example usage:
(link: "Stake")[The dracula crumbles to dust.]
will create a link reading "Stake" which, when clicked, disappears and shows "The dracula crumbles to dust."(link: "Click to continue")[==
will create a link that, using the unclosed hook syntax, defers the display of the remainder of the passage until it is clicked.
Rationale:
As you're aware, links are what the player uses to traverse your story. However, links can also be used to simply display text or run macros inside hooks. Just attach the (link:) macro to a hook, and the entire hook will not run or appear at all until the player clicks the link.
Note that this particular macro's links disappear when they are clicked - if you want their words to remain in the text, or for only a small portion of the text to disappear, consider using (link-reveal:).
Details:
This creates a link which is visually indistinguishable from normal passage links. However, a changer can optionally be given, after the string, to change the appearance of the link. This must be a changer that would be accepted by (enchant-in:) or (link-style:) - which is to say, (link:), (replace:), (append-with:), or any of their relatives cannot be given, or else an error will result. Note that if you wish to apply a changer to every link in the passage (or, via the use of a 'header' or 'footer' tagged passage, every link in the story), then you can simply use (enchant:) with ?Link instead.
The created link is displayed in place of the hook's contents, and is exempt from all changers that would normally apply to the hook. This means that changers like (text-colour:), added to this changer, will ONLY apply to the hook once it's revealed, and not the link itself. To apply changers to just the link, consider wrapping it in a hook itself and using (link-style:), such as by the following:
(link-style: (text-colour:red))[(link:"Hands stained red")[Hands pure and dry :)]]
Alternatively, you can just use (enchant:) with ?Link
to enchant every link with the same changer.
See also:
(link-reveal:), (link-rerun:), (link-repeat:), (link-goto:), (click:), (more:)
The (link-reveal: ) macro
(link-reveal: String, [Changer]) → Changer
Also known as: (link-append:)
When attached to a hook, this replaces the hook with a link that has the given text. The link, when clicked, reveals the hook and becomes plain, unstyled text. An optional changer can be given to alter the style of the link (instead of altering the style of the attached hook).
Example usage:
(link-reveal: "Heart")[broken]
will create a link reading "Heart"
which, when clicked, changes to plain text, and shows "broken" after it.
Rationale:
This is similar to (link:), but allows the text of the link to remain in the passage after it is clicked. It allows key words and phrases in the passage to expand and reveal more text after themselves. Simply attach it to a hook, and the hook will only be revealed when the link is clicked.
Details:
This creates a link which is visually indistinguishable from normal passage links. However, a changer can optionally be given, after the string, to change the appearance of the link. This must be a changer that would be accepted by (enchant-in:) or (link-style:) - which is to say, (link:), (replace:), (append-with:), or any of their relatives cannot be given, or else an error will result. Note that if you wish to apply a changer to every link in the passage (or, via the use of a 'header' or 'footer' tagged passage, every link in the story), then you can simply use (enchant:) with ?Link instead.
The created link is displayed in place of the hook's contents, and is exempt from all changers that would normally apply
to the hook. This means that changers like (text-colour:), added to this changer, will ONLY apply
to the hook once it's revealed, and not the link itself. To apply changers to just the link, consider wrapping it in a
hook itself and using (link-style:), or just using (enchant:) with ?Link
to enchant every link.
If the link text contains formatting syntax, such as "bold", then it will be retained when the link is demoted to text.
See also:
(link:), (link-rerun:), (link-repeat:), (link-goto:), (click:), (more:)
The (link-repeat: ) macro
(link-repeat: String, [Changer]) → Changer
When attached to a hook, this replaces the hook with a link that has the given text. The link, when clicked, reveals the hook. Further clicks will cause the hook to repeat itself - a copy of the hook's code will be run, and the result appended to it, in a manner similar to (for:). An optional changer can be given to alter the style of the link (instead of altering the style of the attached hook).
Example usage:
(link-repeat: "Add cheese")[(set:$cheese to it + 1)]
will create a link reading "Add cheese" which, when clicked, adds 1 to the $cheese variable using (set:), and can be clicked repeatedly.(link-repeat: "Scream a little ")[A]
will, when the link is clicked, add an A to the hook each time.
Rationale:
This is similar to (link:), but allows the created link to remain in the passage after it is clicked. It can be used to make a link that fills with increasingly more text after each click, possibly conveying a sense of powerlessness or desperation.
This macro is part of a pair with (link-rerun:) - the latter macro will empty the hook each time the link is clicked. This one should be used if you'd prefer the hook to retain each of its past runs.
The created link is displayed in place of the hook's contents, and is exempt from all changers that would normally apply
to the hook. This means that changers like (text-colour:), added to this changer, will ONLY apply
to the hook once it's revealed, and not the link itself. To apply changers to just the link, consider wrapping it in a
hook itself and using (link-style:), or just using (enchant:) with ?Link
to enchant every link.
Details:
This creates a link which is visually indistinguishable from normal passage links. However, a changer can optionally be given, after the string, to change the appearance of the link. This must be a changer that would be accepted by (enchant-in:) or (link-style:) - which is to say, (link:), (replace:), (append-with:), or any of their relatives cannot be given, or else an error will result. Note that if you wish to apply a changer to every link in the passage (or, via the use of a 'header' or 'footer' tagged passage, every link in the story), then you can simply use (enchant:) with ?Link instead.
See also:
(link-rerun:), (link-reveal:), (link:), (link-goto:), (click:)
The (link-rerun: ) macro
(link-rerun: String, [Changer]) → Changer
When attached to a hook, this replaces the hook with a link that has the given text. The link, when clicked, reveals the hook. Further clicks will cause the hook to rerun itself, as if by the effect of (rerun:). An optional changer can be given to alter the style of the link (instead of altering the style of the attached hook).
Example usage:
(link-rerun: "ROLL DICE ")[You rolled a (random:1,6).]
will create a link reading "ROLL DICE" which, when clicked, changes the hook to "You rolled a " followed by a random number between 1 and 6.
Rationale:
This is similar to (link:), but allows the created link to remain in the passage after it is clicked. It can be used to make a link which displays a slightly varying run of prose over and over, or a link which must be clicked multiple times before something can happen (using (set:) and (if:) to keep count of the number of clicks).
This macro is part of a pair with (link-repeat:) - the latter macro will append each run of the hook, so that text gradually accumulates within it. This one should be used if you'd prefer the hook to remain at a certain size, or need it to always naturally flow from the link text.
The created link is displayed in place of the hook's contents, and is exempt from all changers that would normally apply to the hook. This means that changers like (text-colour:), added to this changer, will ONLY apply to the hook once it's revealed, and not the link itself. To apply changers to just the link, provide them (added together if there are multiple) as the optional second value to this macro.
Details:
This creates a link which is visually indistinguishable from normal passage links. However, a changer can optionally be given, after the string, to change the appearance of the link. This must be a changer that would be accepted by (enchant-in:) or (link-style:) - which is to say, (link:), (replace:), (append-with:), or any of their relatives cannot be given, or else an error will result. Note that if you wish to apply a changer to every link in the passage (or, via the use of a 'header' or 'footer' tagged passage, every link in the story), then you can simply use (enchant:) with ?Link instead.
See also:
(link-repeat:), (link-reveal:), (link:), (link-goto:), (click:)
The (link-goto: ) macro
(link-goto: String, [String]) → Command
Takes a string of link text, and an optional destination passage name, and makes a command to create a link that takes the player to another passage. The link functions identically to a standard link.
Example usage:
(link-goto: "Enter the cellar", "Cellar")
is approximately the same as[[Enter the cellar->Cellar]]
.(link-goto: "Cellar")
is the same as[[Cellar]]
.
Rationale:
This macro serves as an alternative to the standard link syntax ([[Link text->Destination]]
), and has a key difference:
The link syntax lets you supply a fixed text string for the link, and a markup expression for the destination
passage's name. (link-goto:) also allows the link text to be any expression - so, something like
(link-goto: "Move " + $name + "to the cellar", "Cellar")
can be written.
Details:
If the passage name doesn't correspond to any existing passage, a broken link (a red link that can't be clicked) will be created.
The resulting command from this macro, like all commands, can be saved and used elsewhere. If you have a complicated link you need to use in several passages, you could (set:) it to a variable and use that variable in its place.
As a bit of trivia... the Harlowe engine actually converts all standard links into (link-goto:) macro calls internally - the link syntax is, essentially, a syntactic shorthand for (link-goto:).
Note that (link-goto:), unlike (link:), doesn't accept a changer value to style the produced link. This is because, as this produces a command (and not a changer), you can simply attach changers to the front of it to style the link.
See also:
(link:), (link-reveal:), (link-undo:), (goto:)
The (link-reveal-goto: ) macro
(link-reveal-goto: String, [String], [Changer]) → Changer
This is a convenient combination of the (link-reveal:) and (go-to:) macros, designed to let you run commands like (set:) just before going to another passage. The first string is the link text, and the second is the passage name. An optional changer, with which to style the link, can also be provided.
Example usage:
(link-reveal-goto: "Study English", "Afternoon 1")[(set:$eng to it + 1)]
will create a link reading "Study English" which, when clicked, adds 1 to the $eng variable using (set:), and then goes to the passage "Afternoon 1".(link-reveal-goto: "Fight the King of England", "Death")[(dialog:"You asked for it!")]
will create a link reading "Fight the King of England" which, when clicked, displays an alert using (dialog:), and then goes to the passage "Death".
Details:
Note that the (visited:) macro can be used for checking if a passage was visited earlier in the game. So, you don't necessarily need to use this macro to record that the player has visited the destination passage. Generally, you should use this macro only if you need to record that the player used this specific link to visit that passage.
Note also that there's no way to "cancel" traveling to the new passage once the link is clicked, unless you include (go-to:), (undo:), or another such macro inside the hook.
See also:
(link-reveal:), (link:), (link-goto:), (click:)
The (link-undo: ) macro
(link-undo: String, [String]) → Command
Takes a string of link text, and produces a link that, when clicked, undoes the current turn and sends the player back to the previously visited passage. The link appears identical to a typical passage link. An optional second string can be provided, which is shown instead of the link if it's not possible to undo.
Example usage:
(link-undo:"Retreat", "Can't retreat!")
will produce a link reading "Retreat" if undos are available. If not,
the text "Can't retreat!" is displayed instead.
Rationale:
The ability to undo the player's last turn, as an alternative to (go-to:), is explained in the documentation of the (undo:) macro. This macro provides a shorthand for placing (undo:) inside a (link:) attached hook.
You may, as part of customising your story, be using (replace:) to change the ?sidebar, and remove its default "undo" link. If so, you can selectively provide undo links at certain parts of your story instead, by using this macro.
Details:
As with (undo:), (link-storylet:) and such, if undos aren't available (either due to this being the start of the story, or (forget-undos:) being used) then either the optional second string will be displayed instead, or (if that wasn't provided) nothing will be displayed.
If this is used in a passage, and (forget-undos:) is used later in the passage to prevent undoing, then this link's text will automatically be replaced with the optional second string (or disappear if it's not provided). This is similar to how (link-fullscreen:) will update itself if another macro changes the player's fullscreen status.
Note that (link-undo:), unlike (link:), doesn't accept a changer value to style the produced link. This is because, as this produces a command (and not a changer), you can simply attach changers to the front of it to style the link.
See also:
(undo:), (link-goto:), (icon-undo:)
The (link-fullscreen: ) macro
(link-fullscreen: String, String, [String]) → Command
Creates a link that, when clicked, toggles the browser's fullscreen mode and windowed mode. The first string is used as its link text if the browser is currently in windowed mode, and the second string if it's currently in fullscreen mode. The link will automatically update (re-rendering the link text) to match the browser's current fullscreen state. The optional third string is used when fullscreen mode isn't allowed by the browser - if it's absent or an empty string, the link won't be displayed at all in that situation.
Example usage:
(link-fullscreen: "Turn fullscreen on", "Turn fullscreen off", "Fullscreen unavailable")
Rationale:
Modern browsers allow web pages to take up the entire screen of the user, in a manner similar to desktop games. This feature can be useful for immersive or moody stories, such as horror stories, that wish to immerse the player's senses in a certain colour or shade, or to display impactful text that doesn't have to compete for attention from any other screen elements. While it can be more convenient to place an (icon-fullscreen:) in your story's sidebar, this macro can be useful if you remove or replace the sidebar with something else, and can serve as an alternative means of activating fullscreen mode.
The third string is an error message or alternative text you can provide if the browser doesn't allow fullscreen mode to be entered, for whatever reason. If you're using this link in the middle of a sentence, you may want to use this to provide an alternative sentence fragment to fit the sentence.
Details:
When activated, this will make the page's <html>
element be the fullscreen element, not <tw-story>
. This is because, in most browsers,
removing the fullscreen element from the page, however briefly, will deactivate fullscreen mode. Since the (enchant:)
macro, when given ?Page,
will often need to wrap <tw-story>
in another element, those macro calls will deactivate fullscreen mode if <tw-story>
was the fullscreen element.
So, if you have edited the compiled HTML to add elements before and after it, such as a navigation bar, that will remain visible while the story
is in fullscreen mode. Additionally, this means that the Debug Mode panel is still visible when fullscreen mode is activated.
If the third string is present, and the browser reports to Harlowe that fullscreen mode is unavailable, then a string visually identical to a broken link will be displayed, using the third string as its text. This is, by default, a red link that cannot be clicked.
Currently, there is no special functionality or error reporting when the browser reports that fullscreen mode is available, but the attempt to switch to fullscreen mode fails. In that case, the link will simply appear to do nothing.
It is possible to "force" the player into fullscreen by nesting the code for a (goto:) call inside the second string, such as by
(link-fullscreen: "Continue.", "(goto:'Area 2')")
, which causes the (goto:) to be run only when the browser enters fullscreen mode,
then immediately leaving the passage and continuing the story. This is NOT recommended, however, because browsers currently (as of 2020) allow
the user to exit fullscreen mode at any time of their own accord, so a player that's not willing to enter fullscreen mode would simply exit
it soon afterward, and this construction would ultimately accomplish very little.
Note that (link-fullscreen:), unlike (link:), doesn't accept a changer value to style the produced link. This is because, as this produces a command (and not a changer), you can simply attach changers to the front of it to style the link.
See also:
(link-goto:), (link-undo:), (cycling-link:), (icon-fullscreen:), (checkbox-fullscreen:)
The (link-show: ) macro
(link-show: String, ...HookName) → Command
Creates a link that, when clicked, shows the given hidden hooks, running the code within.
Example usage:
But those little quirks paled before (link-show: "her darker eccentricities.", ?twist)
Rationale:
As discussed in the documentation for (show:), that macro is intended as a complement to (click-replace:) (or perhaps (click-append:)). While both let you insert text at a location when a link is clicked, they differ in whether they let the author write the initial text or the replacement text at the location when coding the passage.
Typical (click-append:) usage resembles the following, where the inserted text provides supplementary content to the passage's prose, and is written separately from it:
Ah. You remember her eldest well - [a frail, anxious child]<extra|. Unlikely to raise too much of a fuss.
(click-append: ?extra)[, constantly frowning, mumbling every word they utter, flinching at the slightest noise]
Conversely, typical (show:) usage resembles the following, where the inserted text is a continuation of the passage's prose, and is written together with it:
"Look, it's important to comment even the simplest code...|extra)[ You might remember what it does now, but not at 4:50 PM on Friday \
afternoon, when you're about to push to production and a runtime error shows up in it.]"
You (link-reveal:"struggle to listen.")[(show: ?extra)]
The (link-show:) macro provides a convenient shorthand for the latter example, letting you write the final line as
You (link-show: "struggle to listen.", ?more)
.
Details:
As with most link macros, providing this with an empty link text string will result in an error.
As with (show:) and (click:), providing this with a hook which is already visible, or which doesn't even exist, will NOT produce an error, but simply do nothing. Also, showing a hook that was hidden with (hide:) will not re-run the macros contained within, but simply make visible the hook as it was.
Note that (link-show:), unlike (link:), doesn't accept a changer value to style the produced link. This is because, as this produces a command (and not a changer), you can simply attach changers to the front of it to style the link.
See also:
(show:), (link-reveal:), (click-append:), (more:)
The (link-storylet: ) macro
(link-storylet: [String], Number or Lambda, [String]) → Command
If there are storylets (storylets are passages containing a (storylet:) call) in this story, this will create a link to the first open storylet that matches the passed-in 'where' lambda, or, if a number n was passed in, the nth (or, if negative, nthlast) open storylet. An optional first string can provide the link text - otherwise, the link text is the passage name of the storylet. An optional final string can provide text to display when no such storylet is open currently.
Example usage:
The following creates three links to open storylets.
You look over the paddock as you ponder three ways you may spend the coming day.
* (link-storylet: 1, "//(Unavailable)//")
* (link-storylet: 2, "//(Unavailable)//")
* (link-storylet: 3, "//(Unavailable)//")
The following creates a link to the next storylet which matches the 'where' condition. The link text is always an arrow regardless of which passage is linked. If there is no open storylet that matches, the link doesn't appear at all.
(link-storylet: "→", where its tags contains 'episode')
Rationale:
The standard macro for accessing which storylets are currently open in the story is (open-storylets:). Combined with other macros such as (for:) and (link-goto:), links to storylets can be easily created. This macro provides a shorthand for the most basic case: creating a simple link to the first open storylet, second open storylet, and so forth.
Details:
The position functions similarly to the position number given to (subarray:) - positive numbers will count from the start,
and negative numbers will count from the end. (link-storylet:-1)
will produce a link to the last available storylet (which
will be the one with the least (urgency:) value among open storylets). If the number 0 is given, an error will result.
If there is no storylet available for the link (such as (link-storylet: 6)
when only 4 storylets are currently open) then
the optional final string will be displayed. If it isn't given, nothing will be displayed.
Note that (link-storylet:), unlike (link:), doesn't accept a changer value to style the produced link. This is because, as this produces a command (and not a changer), you can simply attach changers to the front of it to style the link.
The (click: ) macro
(click: HookName or String, [Changer or Lambda]) → Changer
Produces a changer which, when attached to a hook, hides it and enchants the specified target, such that it visually resembles a link, and that clicking it causes the attached hook to be revealed.
Example usage:
There is a small dish of water. (click: "dish")[Your finger gets wet.]
Rationale:
The (link:) macro and its variations lets you make passages more interactive, by adding links that display text when clicked. However, it can often greatly improve your passage code's readability to write a macro call that's separate from the text that it affects. You could want to write an entire paragraph, then write code that makes certain words into links, without interrupting the flow of the prose in the editor.
The (click:) macro lets you separate text and code in this way. Place (click:) hooks at the end of your passages, and have them affect named hooks, or text strings, earlier in the passage.
Details:
Text or hooks targeted by a (click:) macro will be styled in a way that makes them indistinguishable from passage links, and links created by (link:). When any one of the targets is clicked, this styling will be removed and the hook attached to the (click:) will be displayed.
Additionally, if a (click:) macro is removed from the passage, then its targets will lose the link styling and no longer be affected by the macro.
You can add further styling to the "links" produced by (click:) by providing an optional changer or "via" lambda as a second value, similar to (link:)'s optional changer. If a "via" lambda is supplied, then that lambda is used to compute a changer dynamically, based on specifics of each hook that's enchanted, similar to lambdas provided to (enchant:).
Targeting ?Page, ?Passage or ?Sidebar:
When a (click:) command is targeting the ?Page, ?Passage or ?Sidebar, instead of transforming the entire passage text into a link, something else will occur: a blue link-coloured border will surround the area, and the mouse cursor (on desktop browsers) will resemble a hand no matter what it's hovering over.
Clicking a link when a (click:) is targeting the ?Page or ?Passage will cause both the link and the (click:) to activate at once.
Using multiple (click:) commands to target the ?Page or ?Passage will require multiple clicks from the player to activate all of them. They activate in the order they appear on the page - top to bottom.
See also:
(link:), (link-reveal:), (replace:), (click-replace:)
The (click-replace: ) macro
(click-replace: HookName or String, [Changer or Lambda]) → Changer
A special shorthand combination of the (click:) and (replace:) macros, this allows you to make a hook
replace its own text with that of the attached hook whenever it's clicked. (click: ?1)[(replace:?1)[...]]
can be rewritten as (click-replace: ?1)[...]
.
Example usage:
My deepest secret.
(click-replace: "secret")[longing for you]
See also:
(click-prepend:), (click-append:)
The (click-rerun: ) macro
(click-rerun: HookName or String, [Changer or Lambda]) → Changer
A special version of the (click:) macro which allows the enchanted hook or text (specified by the first value) to be activated multiple times to re-run the attached hook.
Example usage:
The only place you haven't searched yet is the washing basket, and you know there's nothing to find in there.
(set:_t to 0)\
(click-rerun:"washing basket")[(set:_t to it+1)You pull out (nth:_t, "two left socks","a tie-dyed tie","a thimble","a laced tablecloth"). Just in case it was under there.]
Rationale:
While the (click:) macro lets you add links to your text without placing lots of macro code in the middle of your prose, there isn't an obvious way of creating a repeatable link, similar to (link-rerun:) or using (link:) with (rerun:), in the same way. This macro provides that functionality.
Details:
This changes the enchanted text into a link in the same way as (click:). As with most link macros, you may style the link by providing a changer (or a lambda producing a changer) as the second value.
See also:
(link-rerun:), (click:), (rerun:)
The (click-append: ) macro
(click-append: HookName or String, [Changer or Lambda]) → Changer
A special shorthand combination of the (click:) and (append:) macros, this allows you to append
text to a hook or string when it's clicked. (click: ?1)[(append:?1)[...]]
can be rewritten as (click-append: ?1)[...]
.
Example usage:
I have nothing to fear.
(click-append: "fear")[ but my own hand]
See also:
(click-replace:), (click-prepend:)
The (click-goto: ) macro
(click-goto: HookName or String, String) → Command
A special shorthand combination of the (click:) and (go-to:) macros, this allows you to make a hook
or bit of text into a passage link. (click-goto: ?1, 'Passage Name')
is equivalent to (click: ?1)[(goto:'Passage Name')]
Example usage:
Time to get in your crimchair, plug in your crimphones, power up your crimrig and your crimgrip - the next page in your crimming career awaits.
(click-goto: "crim", "Test")
Details:
This construction differs from simply nesting (go-to:) in a hook, as in (click:?page)[(goto:"Stonehenge")]
in one important respect: you can
attach the (t8n-depart:) and (t8n-arrive:) changers to the (click-goto:) command, such as by (t8n-depart:"dissolve")(click-goto:?page, "Stonehenge")
,
and the passage transition will be applied when you click the indicated area. In the former construction, you'd have to attach the (t8n-depart:) and (t8n-arrive:)
macros to the interior (go-to:) command rather than the (click:) command.
See also:
(link-goto:), (mouseover-goto:), (mouseout-goto:)
The (click-undo: ) macro
(click-undo: HookName or String) → Command
A special shorthand combination of the (click:) and (undo:) macros, this allows you to make a hook
or bit of text into a passage link. (click-undo: ?1)
is equivalent to (click: ?1)[(undo: )]
Example usage:
You might have gotten yourself into a pickle that only time travel can get you out of. (click-undo: ?page)
Details:
This will, of course, cause an error if it's encountered on the first turn of the game (when there's nothing to undo).
You can attach the (t8n-depart:) and (t8n-arrive:) changers to (click-undo:), such as by (t8n-depart:"dissolve")(click-undo:?page)
,
and the passage transition will be applied when you click the indicated area. In the former construction, you'd have to attach the (t8n-depart:) and (t8n-arrive:)
macros to the interior (undo:) command rather than the (click:) command.
See also:
(link-undo:), (mouseover-undo:), (mouseout-undo:)
The (click-prepend: ) macro
(click-prepend: HookName or String, [Changer or Lambda]) → Changer
A special shorthand combination of the (click:) and (prepend:) macros, this allows you to prepend
text to a hook or string when it's clicked. (click: ?1)[(prepend:?1)[...]]
can be rewritten as (click-prepend: ?1)[...]
.
Example usage:
Who stands with me?
(click-prepend: "?")[ but my shadow]
See also:
(click-replace:), (click-append:)
The (action: ) macro
(action: String) → Changer
When attached to a link command, or given to a link changer macro as the second value, this changer turns the link into a different kind of interaction element with a different appearance - one that is either activated by hovering the mouse pointer over it, hoving the mouse pointer off of it, or double-clicking it. This does nothing when attached or supplied to a non-link hook.
Example usage:
(action:'mouseover')[[Now isn't the time to think about that. Look!->Test]]
|1>[Hey, c'mover here, cutie.](click:?1, (action:'mouseover'))[ Wanna merge brains over this printer cable?]
(box:"X")|A>[You can't touch me!](click-replace: ?A,(action:'mouseover'))[Aah! That tickles!]
You reach into the box...(click-append: "box...", (action:'mouseover'))[ ...and pull out the final jewel.]
(link:"CORE OVERRIDE",(action:'mouseout'))[Core overridden. The programs are going wild.]
You kiss her on the (link: "lips.",(action:'mouseout'))[mouth that once sneered so cruelly at you.]
Hold my (link-reveal:"hand.",(action:'mouseout'))[ Thank you.]
Rationale:
Even though Harlowe (and Twine in general) is primarily a tool for writing serious non-linear prose works, it is also meant as a tool for playful, experimental, and abstract works where the act of interaction with the text is put into focus. To that end, macros like this one exist to provide alternative, unusual or unexpected ways for the player to interact with links.
Since these actions (especially double-clicking) differ from the usual convention of hyperlinks, it is recommended that your story explains these kinds of links to the player. Or, if you'd prefer to surprise or conceal something from the player, you may choose not to, and leave them to discover these interactions for themselves.
Details:
The string values this accepts are listed below. Note that these strings are case-insensitive and dash-insensitive.
String | Default appearance | Action |
---|---|---|
"mouseover" |
Move the mouse over the link to activate it (or press it on a touch device). | |
"mouseout" |
Move the mouse onto the link, then off it, to activate it (or press it on a touch device). | |
"doubleclick" |
Double-click (or double-press) the link to activate it. | |
"click" |
An unchanged link that is activated by clicking. |
These actions cannot be combined - (action:'doubleclick')+(action:'click')
will only behave like (action:'click')
.
Each of these actions causes the links to have a slightly different sensation and mood to a normal link. "mouseover"
conveys a mood of fragility and spontaneity in your stories, of text reacting to the merest of touches.
"mouseout"
conveys a sense of "pointing" at the element to interact with it rather than "touching" it, and gives a dream-like or unearthly air to scenes or places.
"doubleclick"
requires a more forceful interaction than clicking, and is commonly associated with desktop operating systems and the concept of "opening".
Because this fundamentally changes the manner in which the link is interacted with, this currently does nothing when given to (enchant:), (enchant-in:), (line-style:), or other such macros.
It is not recommended using this with (click:) to enchant a hook which already contains a link.
While you can write something like (click:?Page, (action:"mouseover"))
, the result won't be that interesting: if the mouse pointer is anywhere
on the page, the hook to which the (click:) changer is attached will immediately run.
See also:
The (live: ) macro
(live: [Number]) → Changer
When you attach this changer to a hook, the hook becomes "live", which means that it's repeatedly re-run every certain number of milliseconds, replacing the source inside of the hook with a newly computed version.
Example usage:
{(live: 0.5s)[
(either: "Bang!", "Kaboom!", "Whammo!", "Pow!")
]}
Rationale:
Passage text generally behaves like a HTML document: it starts as code, is changed into a rendered page when you "open" it, and remains so until you leave. But, you may want a part of the page to change itself before the player's eyes, for its code to be re-renders "live" in front of the player, while the remainder of the passage remains the same.
Certain macros, such as the (link:) macro, allow a hook to be withheld until after an element is interacted with. The (live:) macro is more versatile: it re-renders a hook every specified number of milliseconds. If (if:) or (unless:) macros are inside the hook, they of course will be re-evaluated each time.
Details:
Numbers given to macros such as (live:) can be suffixed with ms
or s
to indicate whether you mean milliseconds or
seconds (see the article on number data for more information). If you give a bare number, the macro interprets it as milliseconds.
Live hooks will continue to re-render themselves until they encounter and print a (stop:) macro. (stop:) should be used whenever you don't need to keep the hook "live", to save on processing and passage repainting (which can interfere with clicking, selecting text, and other interactions).
As of Harlowe 3.3.4, when testing your story in Debug Mode, you can alter the speed at which (live:) waits (so as to quickly advance the passage to an important part, or linger on an important part) by using the "Speed" dropdown in the Tools panel. If you change it to 0.5x, (live:) will wait twice as long as instructed: a 1s wait will behave as if it was 2s, and so forth. Changing it to 2x will half (live:)'s wait time, conversely. Note that this option will currently not affect the speed of transitions (using (t8n-delay:) or (t8n-time:)).
A note about timing: due to browser security and resource limitations, modern browsers may arbitrarily increase the delay given to (live:) by about 5-10ms, based on how long the (live:) macro has been running and how hard the CPU is working. More importantly, if the browser tab becomes inactive (such as by the player switching to another tab), modern browsers will often increase the delay by over 1 second, or, if the tab is inactive for a long time, any arbitrary length of time it wishes! In short, there is no guarantee that the time interval given to (live:) will actually be the time that will elapse between renders! Please use this macro with that limitation in mind.
If you want to just display a hook once a certain thing happens (that is, when the condition in an (if:) macro becomes true) and then (stop:), then the (event:) macro may be shorter and easier to use for this. If you want to display a hook after a certain amount of time has passed, then the (after:) macro is almost certainly what you'd prefer to use.
Currently, you cannot attach (live:) to a command (such as in (live:2s)(link-goto:"?")
). You have to wrap the command
in a hook (such as (live:2s)[(link-goto:"?")]
).
See also:
The (stop: ) macro
(stop: ) → Command
This macro, which accepts no arguments, creates a (stop:) command, which is not configurable.
Example usage:
{(set:$packedBags to true)(live: 1s)[
(if: $packedBags)[OK, let's go!(stop:)]
(else: )[(either:"Are you ready yet?","We mustn't be late!")]
]}
Rationale:
Clunky though it looks, this macro serves a single important purpose: inside a (live:) macro's hook, its appearance signals that the macro must stop running. In every other occasion, this macro does nothing.
This command can't have changers attached - attempting to do so will produce an error.
See also:
The (event: ) macro
(event: Lambda) → Changer
Hooks that have this changer attached will only be run when the given condition becomes true.
Example usage:
This example causes a new string to be displayed after some time has passed, or if the (cycling-link:) cycles to a certain value.
(cycling-link: bind _recall, "Dennis?", "Denver?", "Denzel?", "Duncan?", "Danny?", "Denton?")
(event: when time > 10s or _recall is 'Denton?')[==No, you don't remember his name. [[Go east]].
Rationale:
While the (live:) macro is versatile in providing time-based text updating, one of its common uses - checking if some variable has changed using (if:), and then displaying a hook and stopping the macro with (stop:) - is rather cumbersome. This macro provides that functionality in a shorter form - the example above is roughly equivalent to:
(cycling-link: bind _recall, "Dennis?", "Denver?", "Denzel?", "Duncan?", "Danny?", "Denton?")
{(live: )[
(if: time > 10s or _recall is 'Denton?')[
No, you don't remember his name. [[Go east]].(stop: )
]
]}
Details:
This macro only takes a "when" lambda, which is like a "where" lambda but with "where" changed to "when" for readability purposes. This lambda doesn't have a temp variable before "when" - it doesn't iterate over anything, except, perhaps, moments in time.
Because (event:) hooks only run once, the (stop:) macro is unnecessary here.
Currently, you cannot attach (event:) to a command (such as in (event: when $a is 1)(link-goto:"?")
). You have to wrap the command
in a hook (such as (event:when $a is 1)[(link-goto:"?")]
).
See also:
The (after: ) macro
(after: Number, [Number]) → Changer
Hooks that have this changer attached will only be run once the given amount of time has passed since the passage was rendered. An optional second number specifies that the player can speed up the delay by holding down a keyboard key or mouse button, or by touching the touch device.
Example usage:
This example makes 3 additional hooks appear, one by one. The delays can only be sped up if the passage has been visited before.
The time + 2s
idiom is a convenient way to express that each hook is displayed 2 seconds after the last one was displayed (as the time
identifier
tracks the time passed since the passage was rendered, not the containing hook).
There she was. (after: 2s, (cond: visits > 0, 200ms, 0))[=
Covered in fur, (after: time + 2s, (cond: visits > 0, 200ms, 0))[=
sitting on all fours, (after: time + 2s, (cond: visits > 0, 200ms, 0))[=
and howling at the moon.
Rationale:
This macro is a shorthand form of (event:) that only is given an amount of time to wait. (after:2s)
is the same as (event: when time > 2s)
.
It is also similar to (live:), except that it only runs the hook at most once.
One significant difference this has over (event:) is that it can offer the player the ability to speed up transitions. Frequently asking the player to wait for timed delays can be detrimental to the pacing of a story, especially if they are revisiting earlier passages, and providing an option to skip or expedite them is often greatly appreciated.
Details:
Numbers given to macros such as (after:) can be suffixed with ms
or s
to indicate whether you mean milliseconds or
seconds (see the article on number data for more information). If you give a bare number, the macro interprets it as milliseconds.
The optional second number given is an amount of milliseconds (or, if suffixed with s
, seconds) to advance the transition. For each
millisecond of the transition, Harlowe checks if a key or button is held, and if so, then it is advanced
by the given number (in addition to the elapsed millisecond).
As of Harlowe 3.3.4, when testing your story in Debug Mode, you can alter the speed at which (after:) waits (so as to quickly advance the passage to an important part, or linger on an important part) by using the "Speed" dropdown in the Tools panel. If you change it to 0.5x, (after:) will wait twice as long as instructed: a 1s wait will behave as if it was 2s, and so forth. Changing it to 2x will half (after:)'s wait time, conversely. Note that this option will currently not affect the speed of transitions (using (t8n-delay:) or (t8n-time:)).
A note about timing: due to browser security and resource limitations, modern browsers may arbitrarily increase the delay given to (after:) by about 5-10ms, based on how long the (after:) macro has been in the passage, and how hard the CPU is working. More importantly, if the browser tab becomes inactive (such as by the player switching to another tab), modern browsers will often increase the delay by over 1 second, or, if the tab is inactive for a long time, any arbitrary length of time it wishes! In short, there is no guarantee that the time interval given to (after:) will actually be the time that will elapse before the hook appears! Please use this macro with that limitation in mind.
See also:
(live:), (event:), (more:), (transition-skip:)
The (after-error: ) macro
(after-error: ) → Changer
A bug-specific event macro, this hides the hook and only causes it to run once an error occurs.
Example usage:
(after-error:)[
(dialog:"Sorry, folks, seems like I messed up.
DM me on GregsGameMakingBungalow with a screenshot!")
]\
(link:"Click here for an error")[Can't use ( ) here!]
Rationale:
While you should generally test your story enough to be fairly certain that no error messages will occur, it can sometimes be difficult to be absolutely certain. As such, you may want to be prepared for the worst. If an error message does occur, you might want to show a custom message of your own to the player, instructing them to report the bug in your story, or giving them a chance to continue the story from a later passage. (You may want to use this in a "header" or "footer" tagged passage, so that this notification message may appear anywhere in the story.)
Alternatively, you may want to use it during testing, by combining it with the (debug:) command macro, so that the Debug Mode panel will pop up when the first error occurs, potentially letting testers obtain deeper information about the game's current state.
Details:
This will only display the attached hook at the moment an error message is displayed. If this is used inside a larger hook which only appears after an error is displayed, its attached hook won't appear until another error occurs.
The (more: ) macro
(more: ) → Changer
Hooks that have this changer attached will only be run once no other exits - links and (click:)-enchanted areas - are remaining in the passage, and will reveal "more" prose.
Example usage:
(link:"Look at the duck.")[The duck drifts on the lake's surface.]
(link:"Look at the clouds.")[Seems like rain, which is bad news for just one of you.]
(more:)[You've looked at the duck, and the clouds.]
Rationale:
It's common to use hook-revealing macros like (link:) to provide elaboration on a scene, which the player can encounter in any order
they wish. You may want to require each of these elaborations and details be visited by the player, only displaying the link to
the next passage (or further story-setting material) after they have all been explored. You could implement this using (event: when exits is 0)
,
but this macro, (more:), provides a shorter and more readable alternative.
Details:
This is functionally identical to (event: when exits is 0)
. For more information on what is and is not considered an "exit", see the article
for the "exits" keyword.
If multiple (more:) elements are in the passage, they will appear in the order they appear. This may cause earlier ones to reveal
links inside their hooks, and thus "block" the subsequent ones from revealing. In the case of
(more:)[You see [[an exit]] ahead.] (more:)[But you hear chuckling behind you...]
, the first (more:) hook will reveal
a passage link, thus causing the second hook to not be revealed.
See also:
The (abs: ) macro
(abs: Number) → Number
This maths macro finds the absolute value of a number (without the sign).
Example usage:
(abs: -4)
produces 4.
The (cos: ) macro
(cos: Number) → Number
This maths macro computes the cosine of the given number of radians.
Example usage:
(cos: 3.14159265)
produces -1.
The (exp: ) macro
(exp: Number) → Number
This maths macro raises Euler's number to the power of the given number, and provides the result.
Example usage:
(exp: 6)
produces approximately 403.
The (log: ) macro
(log: Number) → Number
This maths macro produces the natural logarithm (the base-e logarithm) of the given number.
Example usage:
(log: (exp:5))
produces 5.
The (log10: ) macro
(log10: Number) → Number
This maths macro produces the base-10 logarithm of the given number.
Example usage:
(log10: 100)
produces 2.
The (log2: ) macro
(log2: Number) → Number
This maths macro produces the base-2 logarithm of the given number.
Example usage:
(log2: 256)
produces 8.
The (max: ) macro
(max: ...Number) → Number
This maths macro accepts numbers, and evaluates to the highest valued number.
Example usage:
(max: 2, -5, 2, 7, 0.1)
produces 7.
The (min: ) macro
(min: ...Number) → Number
This maths macro accepts numbers, and evaluates to the lowest valued number.
Example usage:
(min: 2, -5, 2, 7, 0.1)
produces -5.
The (pow: ) macro
(pow: Number, Number) → Number
This maths macro raises the first number to the power of the second number, and provides the result.
Example usage:
(pow: 2, 8)
produces 256.
The (sign: ) macro
(sign: Number) → Number
This maths macro produces -1 when given a negative number, 0 when given 0, and 1 when given a positive number.
Example usage:
(sign: -4)
produces -1.
The (sin: ) macro
(sin: Number) → Number
This maths macro computes the sine of the given number of radians.
Example usage:
(sin: 3.14159265 / 2)
produces 1.
The (sqrt: ) macro
(sqrt: Number) → Number
This maths macro produces the square root of the given number.
Example usage:
(sqrt: 25)
produces 5.
The (tan: ) macro
(tan: Number) → Number
This maths macro computes the tangent of the given number of radians.
Example usage:
(tan: 3.14159265 / 4)
produces approximately 1.
The (go-to: ) macro
(go-to: String) → Command
This command stops passage code and sends the player to a new passage, starting a new turn as if a passage link was clicked. If the passage named by the string does not exist, this produces an error.
Example usage:
(go-to: "The Distant Future")
Rationale:
There are plenty of occasions where you may want to instantly advance to a new passage without the player's volition. (go-to:) provides access to this ability.
(go-to:) can accept any expression which evaluates to
a string. You can, for instance, go to a randomly selected passage by combining it with
(either:) - (go-to: (either: "Win", "Lose", "Draw"))
.
(go-to:) can be combined with (link:) to accomplish the same thing as (link-goto:):
(link:"Enter the hole")[(go-to:"Falling")]
However, you
can include other macros inside the hook to run before the (go-to:), such as (set:),
(put:) or (save-game:).
Details:
You can attach changers like (t8n-depart:) and (t8n-arrive:) to this to alter the transition animation used when (go-to:) activates. Other kinds of changers won't do anything, though.
If it is performed, (go-to:) will "halt" the passage and prevent any macros and text after it from running. So, a passage that contains:
(set: $listen to "I love")
(go-to: "Train")
(set: $listen to it + " you")
will not cause $listen
to become "I love you"
when it runs.
You should generally avoid using (go-to:) unconditionally in the passage - that is, avoid using it such that it would immediately run when the player enters, regardless of anything. This has a few side-effects: it makes it difficult to use (undo:) to return to this passage, and it counts as a new turn for the "turns" identifier even though the player didn't do or see anything. You can use (redirect:) in place of (go-to:) to avoid these issues.
If you simply want to go back to the previous passage, forgetting the current turn, then you may use (undo:).
See also:
(link-goto:), (undo:), (redirect:)
The (redirect: ) macro
(redirect: String) → Command
This command sends the player to a new passage. However, unlike (goto:), this does not start a new turn - undoing after this will send the player to the turn before the redirect occurred.
Example usage:
(redirect: "Workdesk")
Rationale:
(go-to:) is useful for transferring the player to a new passage after performing some interaction or waiting for some (event:), but is less useful for transferring the player from a passage unconditionally. Attempting to undo a turn, using (undo:) or (link-undo:), will simply cause the (go-to:) to activate again immediately, nullifying the attempt to undo the turn.
While it's possible to use (display:) in place of a (go-to:), displaying the next passage instead of navigating to it, there are a few problems: the displayed passage won't be the name produced by (passage:), it won't be present in the (history:) array, the header and footer passages won't be re-run, and, of course, the text of the original passage remains onscreen.
(redirect:) exists as an alternative to (go-to:) that avoids these problems. Furthermore, a use of (redirect:) typically indicates to other readers of the code that the change of passages is for technical reasons, not for in-story reasons, so the meaning of the two is distinct.
Details:
When a (redirect:) macro is used, the departing passage (the one containing the (redirect:) call)
remains in the (history:) array, and is still considered visited by visits
. However, no additional
turn will result, and turns
will not be affected.
Also, temp variables that were (set:) in the departing passage will not be accessible in the new passage.
Transition changers that can be attached to (go-to:) can be attached to (redirect:), including (t8n-depart:) and (t8n-arrive:).
If it is performed, (redirect:) will "halt" the passage and prevent any macros and text after it from running. So, a passage that contains:
(set: $listen to "I love")
(redirect: "Train")
(set: $listen to it + " you")
will not cause $listen
to become "I love you"
when it runs.
See also:
The (undo: ) macro
(undo: [String]) → Command
This command stops passage code and "undoes" the current turn, sending the player to the previous visited passage and forgetting any variable changes that occurred in this passage. You may provide an optional string to display if undos aren't available.
Example usage:
You scurry back whence you came... (after:2s)[(undo:)]
will undo the current turn after 2 seconds.
Rationale:
The (go-to:) macro sends players to different passages instantly. But, it's common to want to send players back to the passage they previously visited, acting as if this turn never happened. (undo:) provides this functionality.
By default, Harlowe offers a button in its sidebar that lets players undo at any time, going back to the beginning of the game session. However, if you wish to use this macro, and only permit undos in certain passages and occasions, you may remove the button by using (replace:) on the ?sidebar in a header tagged passage.
Details:
You can attach changers like (t8n-depart:) and (t8n-arrive:) to this to alter the transition animation used when (undo:) activates. Other kinds of changers won't do anything, though.
If undos aren't available (either due to this being the start of the story, or (forget-undos:) being used) then either the optional second string will be displayed (as markup) instead, or (if that wasn't provided) nothing will be displayed.
If the previous turn featured the use of (redirect:), (undo:) will take the player to the passage in which the turn started, before any (redirect:) macros were run.
Just like (go-to:), (undo:) will "halt" the passage and prevent any macros and text after it from running.
See also:
(go-to:), (link-undo:), (icon-undo:)
The (restart: ) macro
(restart: ) → Command
Also known as: (reload:)
When this command is used, the player's browser will immediately attempt to reload the page, in effect restarting the entire story.
Example usage:
(click:"Restart")[(restart:)]
Details:
Normally, Harlowe stories will attempt to preserve their current game state across browser page reloads. This macro will suppress this behaviour, guaranteeing that the story restarts from the beginning.
In order to prevent an endless "restart loop", this macro can't be used in the first passatge of the story. Attempting to do so will cause an error.
Using (restart:) will not erase any games that have been saved with (save-game:).
This command can't have changers attached - attempting to do so will produce an error.
See also:
(icon-restart:), (forget-undos:)
The (ceil: ) macro
(ceil: Number) → Number
This macro rounds the given number upward to a whole number. If a whole number is provided, it returns the number as-is.
Example usage:
(ceil: 1.1)
produces 2.
The (floor: ) macro
(floor: Number) → Number
This macro rounds the given number downward to a whole number. If a whole number is provided, it returns the number as-is.
Example usage:
(floor: 1.99)
produces 1.
The (num: ) macro
(num: String) → Number
Also known as: (number:)
This macro converts strings to numbers by reading the digits in the entire string. It can handle decimal fractions and negative numbers. If any letters or other unusual characters appear in the number, it will result in an error.
Example usage:
(num: "25")
results in the number 25
.
Rationale:
Unlike in Twine 1 and SugarCube, Twine 2 will only convert numbers into strings, or strings
into numbers, if you explictly ask it to using macros such as this. This extra
carefulness decreases the likelihood of unusual bugs creeping into stories
(such as performing "Eggs: " + 2 + 1
and getting "Eggs: 21"
).
Usually, you will only work with numbers and strings of your own creation, but if you're receiving user input and need to perform arithmetic on it, this macro will be necessary.
See also:
The (random: ) macro
(random: Number, [Number]) → Number
This macro produces a whole number randomly selected between the two whole numbers, inclusive (or, if the second number is absent, then between 0 and the first number, inclusive).
Example usage:
(random: 1,6)
simulates a six-sided die roll.
Details:
This is one of the features that uses Harlowe's pseudo-random number generator. If you use (seed:) at the start of the story, the number will be predetermined based on the seed string, and how many other random macros and features have been used before it.
See also:
(either:), (shuffled:), (seed:)
The (round: ) macro
(round: Number) → Number
This macro rounds the given number to the nearest whole number - downward if its decimals are smaller than 0.5, and upward otherwise. If a whole number is provided, it returns the number as-is.
Example usage:
(round: 1.5)
produces 2.
The (trunc: ) macro
(trunc: Number) → Number
This macro rounds the given number towards zero. This "truncates" the fractional portion of the number, removing it and leaving just the whole portion.
Example usage:
(trunc: 1.5)
produces 1. (trunc: -3.9)
produces 3.
The (p: ) macro
(p: ...String or Datatype) → Datatype
Also known as: (pattern:)
Creates a string pattern, a special kind of datatype that can match complex string structures. The pattern matches the entire sequence of strings or datatypes given, in order.
Example usage:
"Rentar Ihrno" matches (p:(p-many:1,6,alnum),whitespace,(p-many:1,6,alnum))
checks if the string contains 1-6 alphanumeric letters, followed by a space, followed by 1-6 alphanumeric letters.(set:$upperFirst to (p:uppercase,(p-many:lowercase)))(set:$upperFirst-type $name to "Edgar")
creates a custom datatype, $upperFirst, and creates a typed variable using it, called $name.(unpack: $roadName into (p:str,(p-either:'St','Rd','Ln','Ave','Way')-type _roadTitle))
extracts either "St", "Rd", "ln", "Ave", or "Way" from the end of the $roadName string, putting it in _roadTitle, while producing an error if such an ending isn't in $roadName.(p:"$", digit, ...digit) matches "$21000"
checks if the right side is a string consisting of "$" followed by one or more digits.
Rationale:
The contains
operator is useful for searching strings for words, characters or substrings, but it's noticeably limited when you
want to make more sophisticated queries about a string's contents. For instance, what if you want to check if a string begins with any uppercase
letter, followed by only lowercase letters? Or, what if you want to check if a string contains any words inside quotation "
marks? You could design
and write a cumbersome (loop:) hook to compute these using many contains
checks, but there's a much easier way to do so - rather than check if
a string contains
a substring, check if it matches
a pattern that precisely describes what a valid substring should look like.
A suite of macros, starting with the (p:) macro, are available to construct string patterns. Give the (p:) macro an ordered sequence of strings (like "Mr."
)
or datatypes (like whitespace
, alnum
, newline
, uppercase
, lowercase
, or other string pattern macro calls), and it will produce a datatype that, when used with matches
or is a
,
will match a string that exactly fits the given sequence. (p: "The", (p-many:whitespace), "End")
will match the strings "The End", "The End", "The End", and so forth.
(p: uppercase, "1", (p-opt:"A"))
will match "A1", "B1", "A1A", "C1", and so forth. Spread datatypes can be used to represent zero or more of a given string
datatype: ...uppercase
means "zero or more uppercase letters", ...whitespace
means "zero or more whitespace characters" and so forth - though datatypes
that represent multiple characters, such as ...str
, is the same as str
.
You may notice a similarity between these patterns and array/datamap patterns. Arrays and datamaps can be inspected using the matches
operator when
combined with datatypes and the data structure macros (a:) and (dm:) - (a:(a:num,num),(a:num,num)) matches $array
checks that the array in $array contains two arrays that each contain two numbers, all in one line of code. You can't do this with strings, though,
because a string can only hold characters, not arbitrary data like datatypes. So, these macros provide that functionality for strings, too.
String patterns can be used with (unpack:) to unpack parts of a string into multiple variables at once. For instance,
(set: (p: (p-opt:"Dr. "), (p: alnum-type _firstName, whitespace, alnum-type _lastName)-type _fullName) to "Dr. Iris Cornea")
creates three variables, _firstName, _lastName and _fullName, from a single string, and sets them to "Iris", "Cornea", and "Iris Cornea", respectively.
Details:
When (p:), and some (but not all) macros like (p-many:), are given multiple values, it is treated as a sequence. Strings are matched to sequences as follows: first, Harlowe checks if the start of the string matches the first value in the pattern. If it matches, then Harlowe checks if the start of the remaining portion of the string matches the next value in the pattern. When every part of the string has been matched to every one of the values, then the whole string is considered a match for the whole sequence.
For example, in the case of "egg orb" matches (p:"egg",whitespace,"orb")
:
0. Harlowe checks if the start of "egg orb"
matches "egg"
. It does, so the portion that matches "egg"
is excluded, leaving " orb"
.
0. Harlowe checks if the start of " orb"
matches whitespace
. It does, so the portion that matches whitespace
is excluded, leaving "orb"
.
0. Harlowe checks if the start of "orb"
matches "orb"
. It does. As this means every part of the string has been matched to every one of the values, the entire
statement "egg orb" matches (p:"egg",whitespace,"orb")
evaluates to boolean true
.
By default, datatypes produced with this macro (string patterns) will only match strings that entirely match the pattern. (p: ":", (p-opt:"-"),")")
will match ":)"
and ":-)"
,
but not match " :-) "
or "Sorry :)"
. You can use the str
datatype inside (p:) to represent any amount of unimportant characters. Thus, by rewriting the preceding pattern as
(p:str, ":", (p-opt:"-"),")", str)
, you can produce a datatype that matches any string that contains ":)" or ":-)" anywhere inside it. Alternatively, (p:":", (p-opt:"-"),")", str)
can match just strings that start with ":)" or ":-)", and (p:str, ":", (p-opt:"-"),")")
for strings that end with ":)" or ":-)". If you'd rather only compare the start or end of strings in
a case-by-case basis, you can instead take the pattern and see if it matches
the start
or end
of those strings - (p: ":", (p-opt:"-"),")") matches "Sorry :)"'s end
.
Don't forget that you can save individual parts of a string pattern into variables, and use them to construct larger patterns afterward! For instance,
(set: $HTTP to (p: "http", (p-opt:"s"), "://"))
sets $HTTP to a string pattern that matches "http://" or "https://". With that, you can write
checks like (if: $userURL matches (p: $HTTP, "lightside.college/", str))
and (if: $userURL matches (p:$HTTP, "sunglasses.darkweb/", str))
later in your story, without
needing to rewrite the $HTTP pattern each time.
See also:
(p-either:), (p-opt:), (p-many:), (p-not-before:)
The (p-either: ) macro
(p-either: ...String or Datatype) → Datatype
Also known as: (pattern-either:)
Creates a string pattern that matches either of the single strings or datatypes given.
Example usage:
"Lovegyre" matches (p: (p-either: ...$emotions), "gyre")
checks if the string is any of the strings in $emotions (which is assumed to be an array), followed by "gyre".(set: (p-either:"","Hugged","Charmed","Dazed")-type $statusEffect to "")
creates a variable that can only be set to either "Hugged", "Charmed", "Dazed" or the empty string.
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
Like (p-not:), and unlike the other macros, each of this macro's arguments represents a different possible match, not parts of a single sequence. If
you need a possibility to be a sequence of values, you can nest the (p:) macro inside this one, such as in (p-either: (p:str," Crystal"), "N/A")
.
You can use this macro, along with the spread ...
operator, to succinctly check if the string matches one in a set of characters. For example, to
check if a string is a single bracket character, you can write (p-either: ..."[](){}")
, where each bracket character is in a string that gets spread
out into single characters.
Note that while you can use this as the datatype of a TypedVar (as shown previously), you can't nest TypedVars inside this - (set: (p:"A",(p-either:digit-type _d, "X")) to "AX")
will cause an error, because it's ambiguous whether, when the digit-type _d
TypedVar doesn't match, the variable _d should not be set at all (which
is rather counterintuitive) or if it should be set to an empty string (which contradicts its stated digit-type
restriction anyway).
See also:
(p:), (p-ins:), (p-opt:), (p-many:)
The (p-opt: ) macro
(p-opt: ...String or Datatype) → Datatype
Also known as: (pattern-opt:), (p-optional:), (pattern-optional:)
Creates a string pattern that either matches the sequence of strings or datatypes given, or matches the empty string.
Example usage:
(p-opt:"Default Name")
matches either the empty string, or the string "Default Name".(p: $upperFirst, (p-opt:"?"))
matches strings that match the string pattern in $upperFirst, but which might also end in a question mark.
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
When you use this in (unpack:), such as (unpack: "Connie" into (p:(p-opt:"Lord")-type _isLord, str-type _name))
, and the optional pattern doesn't match,
the variable will be set to the empty string "".
Note that while you can use this as the datatype of a TypedVar (as shown previously), you can't nest TypedVars inside this, because it is an optional match - (set: (p:"A",(p-opt:digit-type _d)) to "A")
will cause an error, because it's ambiguous whether, whenever the enclosing (p-opt:) doesn't match, the variable _d should not be set at all (which
is rather counterintuitive) or if it should be set to an empty string (which contradicts its stated digit-type
restriction anyway).
See also:
(p:), (p-ins:), (p-either:), (p-many:)
The (p-many: ) macro
(p-many: [Number], [Number], ...String or Datatype) → Datatype
Also known as: (pattern-many:)
Creates a string pattern that matches the given sequence of strings and datatypes, repeated a given minimum and maximum number of times - or, if these aren't provided, repeated any number of times.
Example usage:
(p: uppercase, (p-many:lowercase))
produces a datatype that matches strings only if they consist of an uppercase letter followed by one or more lowercase letters.(set: (p-many:3,12,alnum)-type $weakPassword to "ABC123")
creates a variable that is only able to hold strings that consist of between 3 and 12 alphanumeric characters.
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
When this macro's output is given to (p:), it will attempt to match (and thus exclude) the greatest permitted amount of repetitions in the string.
So, (p:'g',(p-many:whitespace,'r'),'b')
will match the string 'g r r r r r rb'
because the (p-many:) macro will match " r r r r r r", instead
of potentially only matching " r".
The first optional number represents the minimum number of times the sequence is permitted to repeat within the string. The second optional number represents the maximum number of times. If only the minimum number is present, then it will also serve as the maximum number, limiting the matched strings to only those that match the sequence exactly that many times.
If no optional numbers are given, the default minimum number of matches is 1. If you want the possibility of matching zero occurrences (i.e. this pattern is optional) then either combine this with (p-opt:), or (preferably) simply give the number 0 as the first argument.
If the maximum number is smaller than the minimum number, or if either of them are negative or fractional, an error will be produced.
When you use this in (unpack:) with a minimum of 0, such as (unpack: "No results." into (p-many: 0, newline)-type _newlines)
, and
there are zero matches, the variable will be set to the empty string "".
Note that while you can use this as the datatype of a TypedVar (as shown previously), you can't nest TypedVars inside this if the minimum is 0, because it then becomes an
optional match - (set: (p:"A",(p-many:0, 8, digit-type _d)) to "A")
will cause an error, because it's ambiguous whether, whenever the enclosing (p-many:)
matches zero occurrecnes, the variable _d should not be set at all (which is rather counterintuitive) or if it should be set to an empty string
(which contradicts its stated digit-type
restriction anyway).
See also:
(p:), (p-ins:), (p-either:), (p-opt:), (p-many:)
The (p-not: ) macro
(p-not: ...String or Datatype) → Datatype
Also known as: (pattern-not:)
Given any number of single characters or non-spread datatypes, this creates a string pattern that matches any one character that doesn't match any of those values.
Example usage:
(p-not: digit, ".")
matches any one string character except digits (matched by thedigit
datatype) or a "." character.(p-not:..."aeiou")
matches any one string character except a lowercase vowel. Note that using the spread...
syntax to spread strings into their individual characters is recommended when using this macro.(p:"[", (p-many:(p-not:"]")), "]")
matches "[" followed by any number of characters except "]", followed by a closing "]".
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
Unlike many pattern datatype macros, this will error if given any datatype that could match 0 or 2+ characters. So, passing str
, empty
, any spread datatype like ...digit
,
or any string with more or less than 1 character, will produce an error.
When you use this in (unpack:), such as (unpack: "-" into (p-many:(p-not:'s'))-type _a)
, and the optional pattern doesn't match,
the variable will be set to the empty string "".
While you can use this as the datatype of a TypedVar, you can't nest TypedVars inside this.
See also:
(p:), (p-opt:), (p-not-before:)
The (p-before: ) macro
(p-before: ...String or Datatype) → Datatype
Also known as: (pattern-before:)
Creates a string pattern that matches the empty string, only it is followed by the given sequence of strings or datatypes. This is best used inside another pattern macro like (p:), alongside a pattern to match, where it serves as an extra restriction on that pattern (making it match only if it's "not before" something).
Example usage:
(str-find: (p:(p-many:digit), (p-before:(p-either:"AM","PM"))), "I arrived home at 42nd Street at 11PM and was in bed by 2AM.")
This example produces (a:"11","2")
. Without the (p-before:) call around the (p-either:) call, this would produce (a:"11PM","2AM")
.
Rationale:
While you can already select a continuous span of text by simply providing multiple values to (p:) and the like, this can be inconvenient for macros such as (str-find:) and (str-replaced:) -
if you only want to find a subset of the match (such as just the digits before "AM" or "PM" in the above example), you'll have to strip the unwanted portion off afterward using a dataname like 1stto2ndlast
.
As an alternative, you can use (p-before:) in the pattern to specify a portion of the pattern that should be checked, but not included in the match substring itself.
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
While you can use this as the datatype of a TypedVar, this won't accomplish much, since, as explained, it only matches the empty string. Additionally, you can't nest TypedVars inside this.
See also:
The (p-not-before: ) macro
(p-not-before: ...String or Datatype) → Datatype
Also known as: (pattern-not-before:)
Creates a string pattern that matches the empty string, unless it is followed by the given sequence of strings or datatypes. This is best used inside another pattern macro like (p:), alongside a pattern to match, where it serves as an extra restriction on that pattern (making it match only if it's "not before" something).
Example usage:
(str-replaced: (p: "$", (p-not-before: digit)), "", _text)
produces a copy of the string in the temp variable _text, but with all dollar signs removed, except where the dollar sign is before a digit.(p-many:(p-either:(p: "0", (p-not-before:"0")), (p:"1", (p-not-before:"1")), whitespace))
matches a string with sequences of alternating 0's and 1's, plus whitespace. It matches"0 0 01 10101 101"
but not"0 0 01 10101 110"
.
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
While you can use this as the datatype of a TypedVar, this won't accomplish much, since, as explained, it only matches the empty string. Additionally, you can't nest TypedVars inside this.
See also:
The (p-start: ) macro
(p-start: ...String or Datatype) → Datatype
Also known as: (pattern-start:)
Identical to (p:), except that when used with macros that search for substrings in strings, like (str-find:), (str-replaced:) and (trimmed:), this only matches if the given strings or datatypes appear at the very start of the string.
Example usage:
(str-replaced: (p-start:"Ben:"), "Former Ben:", _text)
produces a copy of the string in the temp variable _text, but if the string begins with "Ben:", it is changed to "Former Ben:". This does not affect any instances of "Ben:" elsewhere in the string.(str-find: (p-start: (p-many:(p-either: digit, "A"))), _text)
examines the string in _text, and, if it begins with either digits or the letter A, produces an array with a string of those digits. Otherwise, it produces an empty array.(trimmed: (p-start: whitespace), _text)
trims off whitespace from the start of _text, but not the end.
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
When this datatype is used with the matches
operator, it is essentially identical to (p:), in the sense that matches
compares an entire string with an entire pattern, rather than just a portion.
See also:
The (p-end: ) macro
(p-end: ...String or Datatype) → Datatype
Also known as: (pattern-start:)
Identical to (p:), except that when used with macros that search for substrings in strings, like (str-find:), (str-replaced:) and (trimmed:), this only matches if the given strings or datatypes appear at the very end of the string.
Example usage:
(str-replaced: (p-end: ...newline), "\n", _text)
produces a copy of the string in the temp variable _text, but if the string ends with many newlines, they are replaced with a single newline. This does not affect multiple newlines elsewhere in the string.(trimmed: (p-end: ...whitespace), _text)
trims off whitespace from the end of _text, but not the start.
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
When this datatype is used with the matches
operator, it is essentially identical to (p:), in the sense that matches
compares an entire string with an entire pattern, rather than just a portion.
See also:
The (p-ins: ) macro
(p-ins: ...String or Datatype) → Datatype
Also known as: (p-insensitive:), (pattern-ins:), (pattern-insensitive:)
Creates a string pattern that matches the sequence of strings or datatypes given, case-insensitively.
Example usage:
"Hocus pocus" matches (p-ins:"hocus", (p-opt:" "), "pocus")
checks if the magic words match the pattern, regardless of any letter's capitalisation.(unpack: "Scp-991" into (p:(p-ins:"SCP-"), ...digit-type _codeNum))
uses (unpack:) to extract "991" from the right-side string, checking if it matched the given prefix case-insensitively first.
Details:
This is part of a suite of string pattern macros. Consult the (p:) article to learn more about string patterns, special user-created datatypes that can match very precise kinds of strings.
When other patterns are given to this, they are treated as if they are case-insensitive. This means that, if (p:"Opus ", (p-either:digit,"Magnum"))
is stored in the variable
$opus, then (p-ins: $opus)
will create a datatype that can match "opus 1", "OPUS 2", "Opus Magnum" and so forth, even though $opus can't.
When the two case-sensitive datatypes uppercase
and lowercase
are given to this, they are treated as if they are anycase
.
When typed variables are used in string patterns, such as in (p-ins: "Side ", (p-either:"A","B")-type _letter)
, the variable's type may sometimes appear to contradict the
case-insensitivity imposed by an enclosing (p-ins:) - if that pattern is matched with "side a", then can "a" be stored in a (p-either:"A","B")-type
variable?.
Harlowe resolves this as follows: when a typed variable is placed inside (p-ins:), its type is wrapped in a (p-ins:) itself. So, _letter in the preceding example
is bound to (p-ins: (p-either:"A","B"))-type
data, instead of just (p-either:"A","B")-type
data.
See also:
(p:), (p-opt:), (p-many:), (p-either:)
The (dialog: ) macro
(dialog: [Bind], String or CodeHook, ...String) → Command
Also known as: (alert:)
A command that, when used, displays a pop-up dialog box with the given string or codehook displayed, and a number of button-shaped links labeled with the remaining other strings. If an optional bound variable is provided, that variable is updated to match the pressed button.
Example usage:
(dialog: [Beyond this point, things get serious. Grab a snack and buckle up.], "Sure.")
(dialog: bind $defund, "Which department will you defund?", "Law Enforcement", "Education", "Health", "Public Housing")
Rationale:
It may seem counterintuitive for such a heavily text-based medium as a hypertext story to have a need for dialog boxes, but they can serve as places to include auxiliary text that's contextually separate from the passage's themes, such as brief updates on characters, tasks and goals, or momentary asides on incidental world details. because they darken and cover the screen when they appear, they are also very useful for displaying and offering especially climactic actions or decisions, such as an irreversible ethical choice.
While there are other dialog box-producing macros, namely (prompt:) and (confirm:), those are meant purely for input-gathering purposes. This is designed to be the most general-use dialog-producing macro, allowing any number of links, and optionally binding the clicked link to a variable.
Details:
There's no difference in behaviour when you provide this with a codehook instead of a string. That being said, codehooks are recommended because
their internal markup is correctly coloured in the Twine editor, and because "
or '
symbols don't need to be escaped (using \
) inside them.
The dialog that is produced is implemented entirely in HTML. User CSS stylesheets can be used to style it, and (enchant:) macros that affect ?Link can affect the dialog links.
In Harlowe versions prior to 3.1.0, this macro used the built-in alert()
function of the browser, but to
support certain newer browsers that no longer offer this function, the macro was changed.
If no button strings are given, a single link reading "OK" will be used. Giving empty strings for any of the links will cause an error.
When the dialog is on-screen, the entire game is essentially "paused" - until it is dismissed, no further computations are performed, links can't be clicked (except links inside the dialog text itself), (click:) enchantments shouldn't work, and (live:) and (event:) macros shouldn't fire.
For obvious reasons, you cannot supply a two-way bound variable to this macro. Doing so will cause an error to result.
From version 3.2.0 on, it is possible to attach changers to this command. (t8n:'slide-up')+(text-rotate-x:25)(dialog:"EMAIL SENT!")
, for instance, produces a dialog
that's tilted upward, and which slides upward when it appears.
See also:
(cycling-link:), (prompt:), (confirm:)
The (confirm: ) macro
(confirm: String or CodeHook, [String], [String]) → Boolean
When this macro is evaluated, a pop-up dialog box is shown with the given string displayed,
as well as two links (whose text can also be provided) to confirm or cancel whatever action
or fact the string tells the player. When it is submitted, it evaluates to the boolean true
if the
confirm link had been clicked, and false
if the cancel link had.
Example usage:
(set: $makeCake to (confirm: "Transform your best friend into a cake?", "Do not", "Please do"))
Details:
The dialog that is produced is implemented entirely in HTML. User CSS stylesheets can be used to style it, and (enchant:) macros that affect ?Link can affect the dialog links.
The order of the two optional strings is: the cancel link text, followed by the confirm link text. If one or neither of these is provided, the defaults for each are "Cancel" and "OK". Giving a blank string for the cancel link will cause that link to disappear. Giving an empty string for the confirm link will cause an error (because that link must be clickable for the dialog to work).
In Harlowe versions prior to 3.1.0, this macro used the built-in confirm()
function of the browser, but to
support certain newer browsers that no longer offer this function, the macro was changed.
When the dialog is on-screen, the entire game is essentially "paused" - until it is dismissed, no further computations are performed, links can't be clicked (except links inside the dialog text itself), (click:) enchantments shouldn't work, and (live:) and (event:) macros shouldn't fire.
See also:
(alert:), (prompt:), (checkbox:)
The (prompt: ) macro
(prompt: String or CodeHook, String, [String], [String]) → String
When this macro is evaluated, a browser pop-up dialog box is shown with the first string displayed, a text entry box containing the second string (as a default value), a confirm link and a cancel link. If the confirm link is clicked, it evaluates to the string in the text entry box. If "Cancel" is clicked, it evaluates to the default value regardless of the entry box's contents.
Example usage:
(set: $name to (prompt: [Your name, please:], "Frances Spayne", "Don't care", "Confirm"))
Details:
The dialog that is produced is implemented entirely in HTML. User CSS stylesheets can be used to style it, and (enchant:) macros that affect ?Link can affect the dialog links.
The order of the two optional strings is: the cancel link text, followed by the confirm link text. If one or neither of these is provided, the defaults for each are "Cancel" and "OK". Giving a blank string for the cancel link will cause that link to disappear. Giving an empty string for the confirm link will cause an error (because that link must be clickable for the dialog to work).
In Harlowe versions prior to 3.1.0, this macro used the built-in prompt()
function of the browser, but to
support certain newer browsers that no longer offer this function, the macro was changed.
When the dialog is on-screen, the entire game is essentially "paused" - until it is dismissed, no further computations are performed, links can't be clicked (except links inside the dialog text itself), (click:) enchantments shouldn't work, and (live:) and (event:) macros shouldn't fire.
A note about player-submitted strings: because most string-printing functionality in Harlowe (the (print:) macro, and putting variable names in bare passage prose) will attempt to render markup inside the strings, a player may cause disaster for your story by placing Harlowe markup inside a (prompt:) string, which, when displayed, produces either an error or some effect that undermines the story. In order to display those strings safely, you may use either the verbatim markup, the (verbatim:) changer, or (verbatim-print:).
See also:
(alert:), (confirm:), (input-box:)
The (replace: ) macro
(replace: ...HookName or String) → Changer
Creates a command which you can attach to a hook, and replace target destinations with the hook's contents. The targets are either text strings within the current passage, or hook references.
Not to be confused with (str-replaced:).
Example usage:
This example changes the words "categorical catastrophe" to "dogegorical dogastrophe"
A categorical catastrophe!
(replace: "cat")[**dog**]
This example changes the |face>
and |heart>
hooks to read "smile":
A |heart>[song] in your heart, a |face>[song] on your face.
(replace: ?face, ?heart)[smile]
Rationale:
A common way to make your stories feel dynamic is to cause their text to modify itself before the player's eyes, in response to actions they perform. You can check for these actions using macros such as (link:), (click:) or (live:), and you can make these changes using macros such as (replace:).
Using (replace:) is only one way of providing this dynamism, however - the (show:) macro also offers similar functionality. See that macro's article for an explanation of when you might prefer to use it over (replace:), and vice-versa.
Details:
(replace:) lets you specify a target, and a block of text to replace the target with. The attached hook (which specifies the replacement text) will not be rendered normally - thus, you can essentially put (replace:) commands anywhere in the passage text without interfering much with the passage's visible text.
If the given target is a string, then every instance of the string in the current passage is replaced with a copy of the hook's contents. If the given target is a hook reference, then only named hooks with the same name as the reference will be replaced with the hook's contents. Use named hooks when you want only specific places in the passage text to change.
If the target doesn't match any part of the passage, nothing will happen. This is to allow you to
place (replace:) commands in footer
tagged passages, if you want them to conditionally affect
certain named hooks throughout the entire game, without them interfering with other passages.
(replace:) (and its variations) cannot affects hooks or text that haven't been printed yet - if the (replace:)
runs at the same time that the passage is appearing (as in, it isn't inside a hook that's delayed (live:), (link:), (show:)
or similar macros), and a hook or line of text appears after it in the passage, the macro won't replace its contents
even if it's a valid target. For example: (replace: "cool")[hot] cool water
won't work because the (replace:) runs immediately,
but cool water (replace: "cool")[hot]
and (event: when time > 5)[(replace: "cool")[hot]] cool water
will.
As a result of the above, putting these in header
tagged passages instead of footer
tagged passages won't
do much good, as they are printed before the rest of the passage.
If you wish to use (replace:) to replace a hook with a copy of its own text, to undo the effects of other (replace:), (append:), (prepend:) or other macros on it, consider using the (rerun:) macro instead.
See also:
(append:), (prepend:), (show:), (rerun:), (more:), (replace-with:)
The (append: ) macro
(append: ...HookName or String) → Changer
A variation of (replace:) which adds the attached hook's contents to the end of each target, rather than replacing it entirely.
Example usage:
(append: "Emily")[, my maid]
adds ", my maid " to the end of every occurrence of "Emily".(append: ?dress)[ from happier days]
adds " from happier days" to the end of any hook tagged with|dress>
.
Rationale:
As this is a variation of (replace:), the rationale for this macro can be found in that macro's description. This provides the ability to append content to a target, building up text or amending it with an extra sentence or word, changing or revealing a deeper meaning.
See also:
(replace:), (prepend:), (show:), (rerun:), (more:), (append-with:)
The (prepend: ) macro
(prepend: ...HookName or String) → Changer
A variation of (replace:) which adds the attached hook's contents to the beginning of each target, rather than replacing it entirely.
Example usage:
(prepend: "Emily")[Miss ]
adds "Miss " to the start of every occurrence of "Emily".(prepend: ?dress)[my wedding ]
adds "my wedding " to the start of any hook tagged with|dress>
.
Rationale:
As this is a variation of (replace:), the rationale for this macro can be found in that macro's description. This provides the ability to prepend content to a target, adding preceding sentences or words to a text to change or reveal a deeper meaning.
See also:
(replace:), (append:), (show:), (rerun:), (more:), (prepend-with:)
The (replace-with: ) macro
(replace-with: String or CodeHook) → Changer
A counterpart to (append-with:) and (prepend-with:), this replaces the entirety of the attached hook with the contents of the given string (or code hook).
Example usage:
(set: $vitalInfoChanger to it + (replace-with:"**This sentence may contain mature content, so we've excised it from your mind.**"))
causes the changer in $vitalInfoChanger, which may have been used previously in the story, to replace the hooks' text with a censorship notification.(set: $locked to (replace-with:[(text-color:red)[Locked Content] - Beat the game to unlock]))
creates a changer in a variable named $locked which, when attached to a hook, replaces it with[(text-color:red)[Locked Content] - Beat the game to unlock]
. This is mostly similar to passing the string"(text-color:red)[Locked Content] - Beat the game to unlock"
, but the contents of the code hook are syntax-highlighted in the passage editor, making it easier to read.
Rationale:
This changer macro may seem unintuitive and without obvious purpose - what is the point of a changer that changes a hook so drastically that nothing is left of its original text, and the player never sees it? However, there are some minor cases where such an effect is helpful: being able to pre-fill an empty hook with a given line of text not only saves you from having to write out that text multiple times (similar to saving that text in a variable by itself and using (print:) or a bare variable to display it), but also allows additional changers to be combined with it, and for (replace:), (append:) and (prepend:) macros to modify it afterward, by targeting the specific name of the attached hook. And, you can, at a later point in a story, add this to an existing changer to cause hooks it formerly changed to display different text content.
Details:
This changer, when attached to a hook, will never allow the prose it replaces to be run - (replace-with:"")[(set:$x to 1)]
will not allow
the enclosed (set:) macro to be run before it is replaced.
This macro can't be used with (enchant:) or (change:) - attempting to do so will produce an error. You'll want to instead use (replace:), which accomplishes the same effect.
See also:
(append:), (prepend:), (append-with:), (prepend-with:), (show:)
The (append-with: ) macro
(append-with: String or CodeHook) → Changer
Creates a changer that, when attached to a hook, adds the contents of the given string (or code hook) to the end of the hook.
Example usage:
(set: $cutie to (append-with:"~♡")+(color:red+white))
creates a changer that causes any attached hook to become pink and have~♡
at the end.(set: $dateStamp to (append-with:[<br>Posted on: $date]))
creates a changer which appends<br>Posted on: $date
to the end of the attached hook. This is mostly identical to providing the string"<br>Posted on: $date"
, but the contents of the code hook are syntax-highlighted in the passage editor, making it easier to read.(set: $mattias to (prepend-with:'MATTIAS:"')+(append-with:'"'))
creates a changer that causes any attached hook to be surrounded withMATTIAS:"
and"
, which would be useful for character dialogue.
Rationale:
Some lines of prose you write in your story will tend to have identical endings, be they punctuation, dialogue tags, or otherwise, which you may tire of repetitively writing. This macro and (prepend-with:) allow you to automatically attach text without having to manually write it in full - simply save this changer to a variable, and attach it to the hook. While, it should be noted, you can use (append:) inside of a "footer" tagged passage to also automate this hook modification, this can, at times, be more convenient than having to modify a separate passage. Also, this macro is especially useful when combined with other changers, such as (text-style:), (font:) or (text-colour:).
Details:
The way this changer amends the text of the hook is similar to how (append:) amends hooks. To be precise, the full text of the hook is
rendered before it is amended with these changers. This means that, among other things, code structures can't cross the boundary between
the appended text and the hook - (append-with:"</b>")[<b>Bold]
will NOT work as it seems - the <b>
tag will not be matched with the </b>
in the appended text.
Multiple (append-with:) and (prepend-with:) changers can be added together. When this combined changer is attached to a hook,
each constituent changer is applied in left-to-right order. So, (append-with:" (5 Favs)")+(append-with:" (2 Reblogs)")[my teeth ate themselves]
will result in the hook reading my teeth ate themselves (5 Favs) (2 Reblogs)
.
This macro can't be used with (enchant:) or (change:) - attempting to do so will produce an error. You'll want to instead use (append:) or (prepend:), which accomplish the same effect of amending a hook or text occurrence remotely.
See also:
(append:), (replace:), (prepend-with:), (replace-with:), (show:)
The (prepend-with: ) macro
(prepend-with: String or CodeHook) → Changer
Creates a changer that, when attached to a hook, adds the contents of a given string (or code hook) to the start of the hook.
Example usage:
(set: $commandPrompt to (prepend-with:">")+(font:"Courier"))
creates a changer which makes the attached hook use a monospace font, with > placed at the start.(set: $dateStamp to (prepend-with:[**$location, $date.**<br>]))
creates a changer which prepends**$location, $date.**<br>
to the start of the attached hook. This is mostly identical to providing the string"**$location, $date.**<br>"
, but the contents of the code hook are syntax-highlighted in the passage editor, making it easier to read.(set: $mattias to (prepend-with:'MATTIAS:"')+(append-with:'"'))
creates a changer that causes any attached hook to be surrounded withMATTIAS:"
and"
, which would be useful for character dialogue.
Rationale:
Some lines of prose you write in your story will tend to have identical beginnings, be they punctuation, dialogue tags, or otherwise, which you may tire of repetitively writing. This macro and (prepend-with:) allow you to automatically attach text without having to manually write it in full - simply save this changer to a variable, and attach it to the hook. While, it should be noted, you can use (prepend:) inside of a "footer" tagged passage to also automate this hook modification, this can, at times, be more convenient than having to modify a separate passage. Also, this macro is especially useful when combined with other changers, such as (text-style:), (font:) or (text-colour:).
Details:
The way this changer amends the text of the hook is similar to how (prepend:) amends hooks. To be precise, the full text of the hook is
rendered before it is amended with these changers. This means that, among other things, code structures can't cross the boundary between
the prepended text and the hook - (prepend-with:"<b>")[Bold</b>]
will NOT work as it seems - the <b>
tag will not be matched with the </b>
.
Multiple (append-with:) and (prepend-with:) changers can be added together. When this combined changer is attached to a hook,
each constituent changer is applied in left-to-right order. So, (prepend-with:"RE:")+(prepend-with:"FWD:")[ARE YOUR EYES UPSIDE-DOWN?]
will result in the hook reading RE:FWD:ARE YOUR EYES UPSIDE-DOWN?
.
This macro can't be used with (enchant:) or (change:) - attempting to do so will produce an error. You'll want to instead use (append:) or (prepend:), which accomplish the same effect of amending a hook or text occurrence remotely.
See also:
(append:), (replace:), (prepend-with:), (replace-with:), (show:)
The (rerun: ) macro
(rerun: ...HookName) → Command
Reruns hooks, restoring them to their original contents, and running the macros within them an additional time.
Example usage:
|1>[You drew a (either:...(range:2,10), "Jack", "Queen", "King", "Ace") of (either:"Hearts","Clubs","Spades","Diamonds").]
(link-rerun:"Shuffle and draw.")[(t8n:"dissolve")(rerun:?1)]
Rationale:
You may often use macros like (replace:) or (append:) to alter the contents of hooks in your passages. But, you may also want an easy way of reversing these changes, to restore the hook to its original state as it had been written in your passage's code. This macro provides a means of doing so without having to reload or revisit the entire passage.
In addition to re-running hooks elsewhere in the passage, you can produce some useful effects by having a (rerun:) affect its containing hook:
|1>[You're nude in the changing room, with only your reflection for company.
(link:"Dress up")[You dress yourself up. Regrettably, you both look worse. (link:"Take off clothes")[(rerun:?1)]]]
Furthermore, as (rerun:) causes macros in the hook to re-run themselves, it can be used to "update" hooks to match the present game state:
(set:$energy to 100)
|1>[Shields: $energy % (text-color:red)[( - $dmg %)]]
(link-rerun: "Take the punch")[(set:$dmg to (either:1,2,3), $energy to it - $dmg)You get punched square in the cockpit!(rerun: ?1)]
Details:
(rerun:) will use the hook's original source as it was written in the passage source - any alterations done to it using (replace:) and other such macros will not be considered.
(rerun:) will re-run every hook with the given name. To only re-run a specific hook, you can use the
possessive syntax, as usual: (rerun: ?daydream's 1st)
.
You can attach a transition changer, such as (transition:), (transition-time:), (transition-delay:), and the rest, to this command. Doing so will cause that transition to be applied to the hook.
(rerun:), unlike (show:), will not work on hidden hooks until they become visible using (show:) or (link-show:).
If you give ?page to (rerun:), an error will result. If you wish to "rerun" the entire page, consider using (restart:).
If you give ?passage to (rerun:), the entire passage's code will be re-rendered. This will not change the value of the visits
keyword, or count as an additional turn in any way.
If you want to rerun a hook multiple times based on elapsed real time, use the (live:) macro.
See also:
(replace:), (show:), (more:), (live:), (animate:)
The (load-game: ) macro
(load-game: String) → Command
This command attempts to load a saved game from the given slot, ending the current game and replacing it with the loaded one. This causes the passage to change.
Example usage:
{(if: (saved-games: ) contains "Slot A")[
(link: "Load game")[(load-game:"Slot A")]
]}
Details:
Just as (save-game:) exists to store the current game session, (load-game:) exists to retrieve a past game session, whenever you want. This command, when given the string name of a slot, will attempt to load the save, completely and instantly replacing the variables and move history with that of the save, and going to the passage where that save was made.
This macro assumes that the save slot exists and contains a game, which you can check by seeing if
(saved-games: ) contains
the slot name before running (load-game:).
This command can't have changers attached - attempting to do so will produce an error.
To avoid a potential infinite loop, whereby (load-game:) loads a game whose current passage contains another (load-game:) call, an error will occur if (load-game:) is run immediately after a game is loaded. ("Immediately" means that the second (load-game:) call occurs with no time delay, such as by (after:), or with no player input, such as by (link:).)
In the event that the saved data exists, but contains an error - for instance, if it refers to a passage which doesn't exist in this story, which could happen if one version of the story is used to save it, and another is used to open it - then a polite dialog box will appear asking the reader whether or not the data should be deleted. An example of such a dialog is below.
Sorry to interrupt... The story tried to load saved data, but there was a problem. The data refers to a passage named 'example', but it isn't in this story.
That data might have been saved from a different version of this story. Should I delete it?
(Type 'delete' and choose OK to delete it.)
Either way, the story will now continue without loading the data.
See also:
The (save-game: ) macro
(save-game: String, [String]) → Boolean
This macro saves the current game's state in browser storage, in the given save slot, and including a special filename. It can then be restored using (load-game:).
Example usage:
##Chapter 2: The Mortuary
(save-game:"Slot A","Chapter 2 start")
Rationale:
Many web games use browser cookies to save the player's place in the game. Harlowe allows you to save the game, including all of the variables that were (set:) or (put:), and the passages the player visited, to the player's browser storage.
(save-game:) is a single operation that can be used as often or as little as you want to. You can include it on every page; You can put it at the start of each "chapter"; You can put it inside a (link:) hook, such as
{(link:"Save game")[
(if:(save-game:"Slot A"))[
Game saved!
](else: )[
Sorry, I couldn't save your game.
]
]}
and let the player choose when to save.
Details:
(save-game:)'s first string is a slot name in which to store the game. You can have as many slots
as you like. If you only need one slot, you can just call it, say, "A"
, and use (save-game:"A")
.
You can tie them to a name the player gives, such as (save-game: $playerName)
, if multiple players
are likely to play this game - at an exhibition, for instance.
Giving the saved game a file name is optional, but allows that name to be displayed by finding it in the (saved-games:) datamap. This can be combined with a (load-game:)(link:) to clue the players into the save's contents:
(link: "Load game: " + ("Slot 1") of (saved-games: ))[
(load-game: "Slot 1")
]
(save-game:) evaluates to a boolean - true if the game was indeed saved, and false if the browser prevented it (because they're using private browsing, their browser's storage is full, or some other reason). Since there's always a possibility of a save failing, you should use (if:) and (else:) with (save-game:) to display an apology message in the event that it returns false (as seen above).
Using the (forget-undos:) macro will, as a side effect of its turn-erasing functionality, reduce the size of the data saved to browser localStorage by (save-game:). However, you should not use that macro solely on the hunch that it provides performance benefits for your story. See the (forget-undos:) macro's article for slightly more details.
See also:
(load-game:), (saved-games:), (forget-undos:)
The (saved-games: ) macro
(saved-games: ) → Datamap
This returns a datamap containing the names of currently occupied save game slots.
Example usage:
(print: (saved-games: )'s "File A")
prints the name of the save file in the slot "File A".(if: (saved-games: ) contains "File A")
checks if the slot "File A" is occupied.
Rationale:
For a more thorough description of the save file system, see the (save-game:) article. This macro provides a means to examine the current save files in the user's browser storage, so you can decide to print "Load game" links if a slot is occupied, or display a list of all of the occupied slots.
Details:
Each name in the datamap corresponds to an occupied slot name. The values are the file names of the files occupying the slot.
The following is an example. If a game was saved using (save-game:"File A", "The Mortuary")
, and there were no other
saved games, the datamap produced by (saved-games:) would look like this.
Name | Value |
---|---|
File A | The string "The Mortuary" |
Changing the datamap does not affect the save files - after this macro has created the datamap, it is simply inert data.
See also:
The (hidden: ) macro
(hidden: ) → Changer
Produces a changer that can be attached to hooks to hide them.
Example usage:
Don't you recognise me? (hidden:)|truth>[I'm your OC, brought to life!]
The above example is the same as
Don't you recognise me? |truth)[I'm your OC, brought to life!]
Rationale:
While there is a way to succinctly mark certain named hooks as hidden, by using parentheses instead of
<
or >
marks, this macro provides a clear way for complex changers to hide their attached hooks.
This works well when added to the (hook:) macro, for instance, to specify a hook's name and visibility
in a single changer.
This macro is essentially identical in behaviour to (if:false)
, but reads better.
This macro takes no values - each changer value it produces is the same.
See also:
The (hide: ) macro
(hide: ...HookName) → Command
Hides a hook, or hooks, that were already visible, without fully erasing them or their contained macro calls.
Example usage:
The exam paper sits before you.
|2>[(link-rerun:"Peek at palm")[(show:?1)(hide:?2)]]
|1)[It says:
(random:10,90)0m, (random:2,10)deg, 1/(either:2,3,4)
(link-rerun:"Hide palm")[(hide:?1)(show:?2)]]
Rationale:
There are times when you need to remove a hook from visibility, but don't want its contents to be forgotten or re-run, as would happen if you used (replace:). The (hide:) macro simply makes a hook invisible, keeping its contents stored as they are until you use (show:) to reveal them again.
Details:
(hide:) will hide every hook with the given names. To only hide a specific hook, you can use the
possessive syntax, as usual: (hide: ?1's 1st)
.
If you want to remove the hook's contents all together, and re-create it anew later, consider using (replace:) and (rerun:) rather than (show:) and (hide:).
If you give ?page to (hide:), an error will result - the entire page can't ever be "hidden".
This command can't have changers attached - attempting to do so will produce an error.
See also:
The (show: ) macro
(show: ...HookName) → Command
Reveals hidden hooks, running the code within if it's not been shown yet.
Example usage:
|fan)[The overhead fan spins lazily.]
(link:"Turn on fan")[(t8n:"dissolve")(show:?fan)]
Rationale:
The purpose of hidden hooks is, of course, to eventually show them - and this macro is how you show them. You can use this command inside a (link:), trigger it in real-time with a (live:) macro, or anywhere else. You can also re-reveal a hook that had been hidden with (hide:), but any macros in that hook won't be re-run.
Using (show:) vs (replace:):
There are different reasons for using hidden hooks and (show:) instead of (replace:). For your stories, think about whether the prose being revealed is part of the "main" text of the passage, or is just an aside. In neatly-coded stories, the main text should appear early in a passage's code, as the focus of the writer's attention.
When using (replace:), the replacement prose is written far from its insertion point. This can improve readability when the insertion point is part of a long paragraph or sentence, and the prose is a minor aside or amendment, similar to a footnote or post-script, that would clutter the paragraph were it included inside. Additionally, (replace:) can be used in a "header" or "footer" tagged passage to affect certain named hooks throughout the story.
You turn away from her, facing the grandfather clock, its [stern ticking]<1| filling the tense silence.
(click-replace: ?1)[echoing, hollow ticking]
When using (show:), the hidden hook's position is fixed in the passage prose. This can improve readability when the hidden hook contains a lot of the "main" text of a passage, which provides vital context and meaning for the rest of the text.
I don't know where to begin... |1)[The weird state of my birth, the prophecy made centuries ago,
my first day of school, the day of the meteors, the day I awoke my friends' powers... so many strands in
the tapestry of my tale, and no time to unravel them.] ...so for now I'll start with when we fell down the hole.
(link:"Where, indeed?")[(show:?1)]
But, there aren't any hard rules for when you should use one or the other. As a passage changes in the writing, you should feel free to change between one or the other, or leave your choice as-is.
Details:
(show:) will reveal every hook with the given name. To only reveal a specific hook, you can use the
possessive syntax, as usual: (show: ?shrub's 1st)
.
You can attach a transition changer, such as (transition:), (transition-time:), (transition-delay:), and the rest, to this command. Doing so will cause that transition to be applied to the hook.
Much like (replace:), (show:) cannot affects hooks or text that haven't been printed yet - if the (show:)
runs at the same time that the passage is appearing (as in, it isn't inside a hook that's delayed by (live:), (link:), (event:)
or similar macros), and a hook or line of text appears after it in the passage, the macro won't replace its contents
even if it's a valid target. For example: (show:?fence)|fence)[A white picket fence.]
won't work because the (show:) runs immediately.
If you provide to (show:) a hook which is already visible, nothing will happen - no error will be produced. If you provide to
(show:) a hook that had been visible, but was hidden with (hide:), then the hook will reappear, but its macros won't be re-run.
If you wish to re-run an already visible hook, use (rerun:). Note that hooks whose visible contents have been replaced with
nothing, such as via (replace: ?1)[]
, are still considered "visible".
If you give ?page to (show:), an error will result - the entire page can't ever be "hidden".
If you wish to reveal a hook after a number of other links have been clicked and removed, such as those created by (link-reveal:) or (click:), you may find the (more:) macro to be convenient.
See also:
(hidden:), (replace:), (rerun:), (more:), (animate:)
The (icon-undo: ) macro
(icon-undo: [String], [String]) → Command
Creates an icon, similar to those in the sidebar, that, if visible and clicked, undoes the current turn, returning to the previous passage, as if by (undo:). It is not visible if undos aren't available.
Example usage:
(replace:?sidebar)[(icon-undo: )(size:2)[(str-repeated:$flowers + 1, "✿ ")]]
alters the sidebar such that there is only an undo button, followed by a number of✿
symbols equal to the number in $flowers plus 1. The space separating each florette symbol allows it to word-wrap normally. This would best be used in a "header" or "footer" tagged passage.(icon-undo:"👈")
creates an element that uses 👈 as its icon instead of the default.(icon-undo:"Undo the turn")
creates an element with the label "Undo the turn" under it.(icon-undo:"👈", "Undo the turn")
combines both of the above.
Rationale:
By default, each passage in a Harlowe story features a narrow sidebar to the left, housing "Undo" and "Redo" menu icons. However, using the (replace:), (append:) or (prepend:) changers with the ?sidebar HookName, it is possible to dynamically change the sidebar, inserting or replacing its contents with any kind of prose. To that end, it is useful to be able to recreate the "Undo" and "Redo" menu icons exactly as they were, in case an earlier operation performed on the sidebar had removed them.
Details:
Of course, you can use this in normal passage prose, if you wish - they are merely commands, just like (link-goto:) or (print:).
If you wish to change the icon to a different symbol, you may provide a string containing a single character to this macro.
If none is given, the default symbol is ↶ (in HTML, ↶
).
You may also provide a string that contains a label for the icon. This label must have more than one character in it (so that it isn't confused with the optional icon string) and will be placed beneath the icon. The label's contents will NOT be interpreted as Harlowe markup, so everything in it will be used verbatim for the label. This is because, unlike links, the label isn't considered part of passage prose.
If both strings given to this macro have more than one character in them, an error will result.
This command creates an element that uses the same default CSS styling as the sidebar's icons: a <tw-icon>
holding a glyph of text at 66px font size,
with 0.2 opacity that changes to 0.4 when hovered over.
Like all sidebar icons, these will automatically hide themselves when they cannot be clicked, leaving a conspicuous space. In the case of the "Undo" icon, it will be hidden if it's the first turn of the game, and there is nothing to undo - or if (forget-undos:) is used to prevent undos. If this is used in a passage, and (forget-undos:) is used later in the passage to prevent undoing, then this will automatically, instantly hide itself.
See also:
(icon-redo:), (undo:), (link-undo:), (click-undo:)
The (icon-redo: ) macro
(icon-redo: [String], [String]) → Command
Creates an icon, similar to those in the sidebar, that, if visible and clicked, "re-does" a turn that was undone. It is only visible if a turn has been undone.
Example usage:
(replace:?sidebar)[(b4r:"solid")+(b4r-color:gray)+(corner-radius:12)(icon-undo: )(b4r:"solid")+(b4r-color:gray)+(corner-radius:12)(icon-redo: )]
alters the sidebar such that the "Undo" and "Redo" icons have rounded borders around them.(icon-redo:"👉")
creates an element that uses 👉 as its icon instead of the default.(icon-redo:"Redo the turn")
creates an element with the label "Redo the turn" under it.(icon-redo:"👉", "Redo the turn")
combines both of the above.
Rationale:
By default, each passage in a Harlowe story features a narrow sidebar to the left, housing "Undo" and "Redo" menu icons. However, using the (replace:), (append:) or (prepend:) changers with the ?sidebar HookName, it is possible to dynamically change the sidebar, inserting or replacing its contents with any kind of prose. To that end, it is useful to be able to recreate the "Undo" and "Redo" menu icons exactly as they were, in case an earlier operation performed on the sidebar had removed them.
Details:
Of course, you can use this in normal passage prose, if you wish - they are merely commands, just like (link-goto:) or (print:).
If you wish to change the icon to a different symbol, you may provide a string containing a single character to this macro.
If none is given, the default symbol is ↷ (in HTML, ↷
).
You may also provide a string that contains a label for the icon. This label must have more than one character in it (so that it isn't confused with the optional icon string) and will be placed beneath the icon. The label's contents will NOT be interpreted as Harlowe markup, so everything in it will be used verbatim for the label. This is because, unlike links, the label isn't considered part of passage prose.
If both strings given to this macro have more than one character in them, an error will result.
This command creates an element that uses the same default CSS styling as the sidebar's icons: a <tw-icon>
holding a glyph of text at 66px font size,
with 0.2 opacity that changes to 0.4 when hovered over.
Like all sidebar icons, these will automatically hide themselves when they cannot be clicked, leaving a conspicuous space. In the case of the "Redo" icon, it will be hidden if this is the latest turn of the game, and there is nothing to "re-do".
See also:
The (icon-fullscreen: ) macro
(icon-fullscreen: [String], [String]) → Command
Creates an icon, similar to those in the sidebar, that, if visible and clicked, toggles fullscreen mode on or off.
Example usage:
(prepend:?sidebar)[(icon-fullscreen: )]
adds a fullscreen icon to the sidebar, above the "undo" and "redo" icons. This would best be used in a "header" or "footer" tagged passage.(icon-fullscreen:"▢")
creates an element that uses ▢ as its icon instead of the default.(icon-fullscreen:"Fullscreen")
creates an element with the label "Fullscreen" under it.(icon-fullscreen:"▢", "Fullscreen")
combines both of the above.
Rationale:
By default, each passage in a Harlowe story features a narrow sidebar to the left, housing "Undo" and "Redo" menu icons. However, other functions may be desirable to have available to the player at all times, such as the capability to toggle fullscreen mode in the browser. While you could place a (link-fullscreen:) or (checkbox-fullscreen:) in your passage prose, placing the icon produced by this macro is a slightly more concise solution that fits with the use of the sidebar for view utility commands.
Details:
Of course, you can use this in normal passage prose, if you wish - they are merely commands, just like (link-goto:) or (print:).
If you wish to change the icon to a different symbol, you may provide a string containing a single character to this macro.
If none is given, the default symbol is ⛶ (in HTML, ⛶
).
You may also provide a string that contains a label for the icon. This label must have more than one character in it (so that it isn't confused with the optional icon string) and will be placed beneath the icon. The label's contents will NOT be interpreted as Harlowe markup, so everything in it will be used verbatim for the label. This is because, unlike links, the label isn't considered part of passage prose.
If both strings given to this macro have more than one character in them, an error will result.
This command creates an element that uses the same default CSS styling as the sidebar's icons: a <tw-icon>
holding a glyph of text at 66px font size,
with 0.2 opacity that changes to 0.4 when hovered over.
When activated, this will make the page's <html>
element be the fullscreen element, not <tw-story>
. This is because, in most browsers,
removing the fullscreen element from the page, however briefly, will deactivate fullscreen mode. Since the (enchant:)
macro, when given ?Page,
will often need to wrap <tw-story>
in another element, those macro calls will deactivate fullscreen mode if <tw-story>
was the fullscreen element.
So, if you have edited the compiled HTML to add elements before and after it, such as a navigation bar, that will remain visible while the story
is in fullscreen mode. Additionally, this means that the Debug Mode panel is still visible when fullscreen mode is activated.
If the browser reports to Harlowe that fullscreen mode is unavailable, then the icon will be hidden, leaving a conspicuous space.
See also:
(link-fullscreen:), (checkbox-fullscreen:)
The (icon-restart: ) macro
(icon-restart: [String]) → Command
Creates an icon, similar to those in the sidebar, that, if visible and clicked, reloads the whole page, restarting the story from the beginning.
Example usage:
(replace:?sidebar)[(icon-restart: )]
replaces the sidebar with just the "reload" icon.
(icon-restart:"⟲")
creates an element that uses F2; as its icon instead of the default.(icon-restart:"Restart")
creates an element with the label "Restart" under it.(icon-restart:"⟲", "Restart")
combines both of the above.
Rationale:
By default, each passage in a Harlowe story features a narrow sidebar to the left, housing "Undo" and "Redo" menu icons. However, other functions may be desirable to have available to the player at all times, such as an option to restart the story from the beginning. This would be best suited to short stories with a high density of random or branching content, such as a story with dozens of options that ends after a certain number of turns, or a procedurally generated puzzle with a lot of dead-ends.
Details:
Of course, you can use this in normal passage prose, if you wish - they are merely commands, just like (link-goto:) or (print:).
If you wish to change the icon to a different symbol, you may provide a string containing a single character to this macro.
If none is given, the default symbol is ⟲ (in HTML, ⟲
).
You may also provide a string that contains a label for the icon. This label must have more than one character in it (so that it isn't confused with the optional icon string) and will be placed beneath the icon. The label's contents will NOT be interpreted as Harlowe markup, so everything in it will be used verbatim for the label. This is because, unlike links, the label isn't considered part of passage prose.
If both strings given to this macro have more than one character in them, an error will result.
This command creates an element that uses the same default CSS styling as the sidebar's icons: a <tw-icon>
holding a glyph of text at 66px font size,
with 0.2 opacity that changes to 0.4 when hovered over.
Normally, Harlowe stories will attempt to preserve their current game state across browser page reloads. This macro will suppress this behaviour, guaranteeing that the story restarts from the beginning.
Clicking this icon will NOT prompt the player with any kind of dialogue box warning them that this will restart the story. Instead, the story will restart without prompting.
See also:
The (icon-counter: ) macro
(icon-counter: Bind, String, [String]) → Command
A command that creates a numeric counter element with a text label, designed to fit in the sidebar, displaying the contents of a number variable (rounded to a whole number as if by (trunc:)), and updating it whenever another macro changes it.
Example usage:
(append: ?sidebar)[(icon-counter: bind $sunseeds, "Sunflower Seeds")]
creates a counter element labeled"Sunflower Seeds"
, which updates to match the number in $sunseeds.(set: $satchel to (dm:"tomato",2))(prepend: ?sidebar)[(icon-counter: bind $satchel's tomato, "Tomato", "Tomatoes")]
creates a counter element labeled"Tomatoes"
if$satchel's tomato
contains a number other than 1, and"Tomato"
otherwise.
Rationale:
The sidebar for Harlowe stories contains two basic gameplay utility functions by default – an (icon-undo:) button and an (icon-redo:) button – and can have more such buttons added using the other icon-related macros, along with changers such as (append:) and (prepend:), and ideally in a header or footer tagged passage. But, one can also use that space to hold status information relevant to the player. If the game features a number of vital numeric values, such as a score or a resource count, having that value be in constant view, and in a relatively consistent screen position, can be very helpful in keeping the player aware of their current status.
This element is visually optimised toward small, whole numeric values, such as the whole numbers from 0 to 100. Numbers with a greater number of decimal places than that can be used, but they will likely exceed the width of the sidebar. Furthermore, decimal places in the value will not be displayed, but will be rounded using the (trunc:) algorithm.
Details:
The optional second string allows for you to provide singular and plural forms of the counter label, which change based on whether the counter is 1 or -1. The first string becomes the singular form of the label, and the second string serves as the plural.
Unlike the other icon-related commands, which create clickable icons, the element this creates cannot be clicked, and is designed to be fully visible at all times. Thus, it does not have 30% opacity by default, but instead has 100% opacity. You may attach the (opacity:) changer to it to lower its opacity, if you wish.
The font used for this element, by default, is Verdana (and falls back to the browser's default sans-serif font family). This is intended to visually differentiate this counter from story prose, which uses a serif font by default.
If, when the element is created, the bound variable is not a number, then an error will result. However, if the bound variable ever changes to a non-number data value after that, then the counter will simply not update, instead of producing an error.
See also:
The (storylet: ) macro
(storylet: Lambda) → Metadata
When placed in a passage, it marks that passage as the beginning of a storylet, using the lambda as the condition upon which it's available to the player, so that other macros, like (open-storylets:), can see and select the passage.
Example usage:
( when $season is "winter" and $married is false and visits is 0)
( when (visited: "mortuary"))
Rationale:
Storylets are mini-stories within a story - disconnected sequences of passages that can be visited non-linearly when certain conditions are fulfilled. They allow a different way of writing interactive fiction than the rigid tree structure of typical Twine games: instead, simply write scenes and events that occur in the story, use this macro in the first passage of these mini-stories to write a programming condition that determines when it would make sense for that scene to occur, and use macros like (open-storylets:) or (link-storylet:) to dynamically create links to the storylets. This authoring style allows content to be added to the story without having to dramatically rearrange the story's structure.
Examples of narrative structures that can be represented as storylets include: jobs on a job board that are available at different times but only acceptable once; encounters in a role-playing-game that vary based on randomness and location; random dream sequences between linear chapters; chores to perform in a housekeeping or farming simulation. Simply create clumps of passages containing each of these sequences, mark the first passage of each with this macro, and make the end of each (or a central "hub" passage that they link back to) with code that uses (open-storylets:) to create links elsewhere.
Details:
This macro adds a "storylet" data name to the (passages:) datamap for this passage, letting you access the passed-in lambda. In fact, you can use (metadata:)
in place of (storylet:) if you wish - ( when $hour is 12)
is actually just a shorthand for ("storylet", when $hour is 12)
. (metadata:)
can be used instead if you're already using it to attach other data. if you use both (storylet:) and ( "storylet"
, an error will result.
Being a metadata macro, a (storylet:) macro call must appear in the passage before every other non-metadata macro in the passage, such as (set:) and (if:).
(This doesn't include macros inside a "header" tagged passage.) The recommended place to put it is at the top of the passage. This restriction is because
the (storylet:) call's lambda is only ever checked outside the passage. Variable-changing macros in the passage, such as (set:), are not run until the
passage is visited, even if they appear before a (storylet:) macro. So, the code (set: $a to 2)( when $a is 2)
is misleading, because it won't
cause $a to always be 2 when the lambda is checked.
Inside a (storylet:) macro's lambda, the "visit" and "visits" identifiers refer to the containing passage, so they will often be 0. Checking visits
(such as by visits is 0
) allows you to make a storylet only be available once (because after that, it will have become visited). Also,
the "exits" identifier cannot be used here (because it's meaningless in this context).
See also:
(open-storylets:), (passages:), (event:), (metadata:)
The (open-storylets: ) macro
(open-storylets: [Lambda]) → Array
Checks all of the (storylet:) macros in every passage, and provides an array of datamaps for every passage whose (storylet:) lambda produced true, sorted by their "urgency" metadata value, then by passage name. If a lambda was provided, the storylets are filtered using it as a search test.
Example usage:
(for: each _p, ...(open-storylets:)'s 1stTo5th)[(link-goto: _p's name) - ]
creates passage links for the first five open storylets.(link-goto: "Off to the next job!", (either: ...(open-storylets: where 'night' is not in its tags))'s name)
creates a single link that goes to a random open storylet.You have (plural: (open-storylets: where its tags contains 'quest')'s length, "quest") available.
displays "You have 3 quests available." if 3 storylets with the "quest" tag are currently open.
Rationale:
For a greater explanation of what storylets are (essentially, disconnected sets of passages that can be procedurally visited when author-specified requirements are met), see the (storylet:) macro's description. This macro is used to create links or listings of storylets which are currently "open" to the player, in combination with other macros such as (for:), (link-goto:) and such.
Details:
The exact algorithm determining the contents and order of the resulting array is as follows.
- First, every passage's "storylet" lambda is run. If it produced true, that passage is added to the array.
- Then, the highest "exclusivity" metadata number among the added passages is found. Each passage with an "exclusivity" lower than that is removed.
- The array is then sorted by each passage's "urgency" metadata number. Ties are then sorted by passage name.
- If the optional "where" lambda was provided, then the results are filtered with it, as if by (find:).
The (urgency:) macro can thus be used in passages to affect their order in this array, and (exclusivity:) can be used to situationally exclude certain passages from it.
The passages returned are datamaps identical to those returned by (passage:). They contain the following names and values.
Name | Value |
---|---|
name | The string name of the passage. |
source | The source markup of the passage, exactly as you entered it in the Twine editor |
tags | An array of strings, which are the tags you gave to the passage. |
storylet | The storylet condition lambda for this passage. |
exclusivity | The exclusivity number, which is used in the algorithm above. Usually added by (exclusivity:). |
urgency | The urgency number, which is used in the algorithm above. Usually added by (urgency:). |
If no passages' storylet requirements are currently met, the array will be empty.
If no passage matches the search lambda given to (open-storylets:), the array will be empty.
If any passage's (storylet:) macro produces an error (such as by dividing a number by 0), it will be displayed when the (open-storylets:) macro is run.
See also:
(storylet:), (link-storylet:), (passages:)
The (exclusivity: ) macro
(exclusivity: Number) → Metadata
When placed in a passage that also has a (storylet:) call, it marks that passage as being more or less "exclusive", meaning that if it's open, it will prevent storylets with lesser exclusivity from appearing in (open-storylets:).
Example usage:
( 2)
means that, if this storylet is open, other storylets with exclusivity lower than this are closed, and can't
appear in (open-storylets:)'s array.
Rationale:
Storylets are very useful for creating non-linear stories, in which the player's available choices and directions are determined entirely by the game state, rather than an explicit web of links. But, sometimes it's necessary to pen the player in and prevent them from having the same range of choices. An example is a climactic final event in a story, which has its own storylet lambda, but which, when available, shouldn't be avoidable by picking another storylet. While you could code this by wording each other passage's storylet lambdas very carefully, such that no others are open when the final event is open, that would be very cumbersome. The (exclusivity:) macro lets you specify that a storylet should be an exclusive option that prevents more common options from being available.
Details:
This is essentially a shorthand for calling (metadata:) with "exclusivity" - it adds an "exclusivity" data name and value to the passage's (passage:) datamap - except that it will error if a non-number is given to it.
Storylets without an "exclusivity" metadata number, added by this macro or by (metadata:), are treated as having ( 0)
. This means
that a storylet with a negative exclusivity, such as ( -0.001)
, will not be able to appear in (open-storylets:) if any other storylets
lacking an explicit (exclusivity:) call are also open.
See also:
The (urgency: ) macro
(urgency: Number) → Metadata
When placed in a passage that also has a (storylet:) call, it marks that passage as being more or less "urgent", meaning that (open-storylets:) will sort it earlier or later than other passages.
Example usage:
( 2)
causes this storylet to appear earlier in the (open-storylets:) than storylets with (1)
or no urgency macro.
Rationale:
The (open-storylets:) macro provides you with an array of all currently-open storylets, but that typically isn't the amount of options
you'd like to show to the player each time. Often you'll just limit it to a few values using array data names like 1stto4th
. In that case, the
order of the returned array matters a lot - being one of the first few values determines whether it'll be seen among the others. In those cases,
it can sometimes be helpful to guarantee a certain storylet or storylets, when available, are always present in the first few values.
The (urgency:) macro allows for this - give it a number, and it will be sorted above open storylets with a lower or no urgency number.
Details:
This is essentially a shorthand for calling (metadata:) with "urgency" - it adds an "urgency" data name and value to the passage's (passage:) datamap - except that it will error if a non-number is given to it.
Storylets without an "urgency" metadata number, added by this macro or by (metadata:), are treated as having ( 0)
. This means
that a storylet with a negative urgency, such as ( -11)
, will appear at the end of the (open-storylets:) array, unless a storylet
with an even lower urgency is also open.
See also:
The (str: ) macro
(str: ...[Number or String or Boolean or Array]) → String
Also known as: (string:), (text:)
(str:) accepts any amount of values and tries to convert them all to a single String.
Example usage:
(print: "YOU NEED: $" + (str: $cash + 200))
converts the number$cash + 200
into a string, so"YOU NEED: $"
can be added to it.
Rationale:
Unlike in Twine 1 and SugarCube, Twine 2 will only convert numbers into strings, or strings into numbers, if you explictly ask it to. This extra carefulness decreases the likelihood of unusual bugs creeping into stories (such as adding 1 and "22" and getting "122"). The (str:) macro offers a quick way to convert non-string values to a string (and its counterpart, (num:), offers the reverse).
Details:
If you give an array to (str:), it will attempt to convert every element
contained in the array to a string, and then join them up with commas. So,
(str: (a: 2, "Hot", 4, "U"))
will result in the string "2,Hot,4,U".
If you'd rather this not occur, you can also pass the array's individual
elements using the ...
operator - this will join them with nothing in between,
as if they were given individually. So, (str: ...(a: 2, "Hot", 4, "U"))
will result in the string "2Hot4U".
If you want to convert numbers into strings in a more sophisticated way, such as by including thousands separators or leading zeros, consider using (digit-format:).
See also:
The (digit-format: ) macro
(digit-format: String, Number) → String
When given a special formatting string, followed by a number, this macro converts the number into a string using the formatter as a guide. "#" characters in the formatting string represent optional digits; "0" characters represent required digits. Other characters are considered either thousands separators or as the decimal point.
Examples:
(digitformat: "###.###", -1234.5678)
produces the string"-234.567"
.(digitformat: "###.###", -1/2)
produces the string"-.5"
.(print: "$" + (digitformat: "##0.00", 0.96))
prints$0.96
.(digitformat: "###,###", 155500)
produces the string"155,500"
. Unlike every other character, commas are assumed to be thousands separators unless a different separator character is used before them.(digitformat: "### ###.", 500000)
produces the string"500 000"
.(altered: via (digitformat: "000", it), ...(range:1,5))
produces(a:"001","002","003","004","005")
.(digitformat: "###.###,", 1255.5)
produces the string"1.255,"
. Note that the German decimal point,
at the end of the string cannot appear in the final string, but serves only to distinguish the.
as being the thousands separator.(digitformat: ".##,###", 1255.5)
produces the string"55,3"
. Note that the German thousands separator at the front of the string cannot appear in the final string (because no0
or#
characters are before it), but serves only to distinguish the,
as being the decimal point.(digitformat: "##,##,###", 2576881)
produces the string"25,76,881"
. This uses Indian numeral separators.
Rationale:
The (str:) macro is a general-purpose conversion macro for creating strings out of other Harlowe datatypes. However, numbers have a number of different writing conventions depending on their context - in English, thousands separators are common for larger numbers, and some contexts, like prices, need trailing or leading zeros. Other languages have different separators than thousands separators, or use different decimal point characters. This macro lets you provide a specific format in which a number is to be converted to a string, allowing the separators, zeros and decimal point to be customised.
Details:
The decimal point in the format string is decided as follows: the rightmost character that isn't #
or 0
is the decimal
point, unless it is ,
and the leftmost character that isn't #
or 0
is also ,
. This is under the assumption that
most Harlowe users will be writing in English (the same assumption used for (str-nth:)) and thus formats like "###,###,###"
intend to use ,
in its English sense as a thousands separator.
If the decimal point has no digits (#
or 0
characters) to its right, or the leftmost separator has no digits to its left,
then they are left off altogether. This can be used to precisely specify the decimal point and separator characters without
requiring them to appear in the final string: "##.###,"
uses a trailing ,
to indicate that ,
is the decimal point, and
.
is the thousands separator.
As a result of these two rules, it is not recommended that you include more formatting or context characters than what is required.
For instance, a format string like "$00.00"
will not cause the final string to have "$", because the $
will be interpreted as
a thousands separator, and thus removed.
If a number bigger than 99999999999999999999 is given, this macro will produce an error.
Errors in decimal representation caused by the underlying browser Javascript platform's floating-point number format,
such as (digitformat: "##.##", 35.3)
producing "35.29", are currently not compensated for by Harlowe.
See also:
The (joined: ) macro
(joined: ...String) → String
Using the first string as a separator value, this macro takes all of the other strings given to it, and joins them into a single string.
Example usage:
(joined:" ", "Philias", "Silus", "Sebastus", "Brotch")
produces the string "Philias Silus Sebastus Brotch".(joined:" or ", ...(dm-values: (dm: "Breakfast", "Toast", "Dinner", "Pasta", "Lunch", "Soup")))
produces the string"Toast or Pasta or Soup"
.
Rationale:
If you have a list of strings stored in an array, which may be the names of related concepts, such as inventory objects or suspect names, you'll often want to display all of them, or a certain number of them, to the player. This will involve adding some kind of separator between them, such as a single space, a line break and bullet point, or something more complicated.
Details:
The separator value will only be used to separate each string value, and won't be appended or prepended to the end of the string.
If only one string is provided (that is, just the separator value) then the empty string will be returned.
The (lowercase: ) macro
(lowercase: String) → String
This macro produces a lowercase version of the given string.
Example usage:
(lowercase: "GrImAcE")
is the same as "grimace"
Details:
The results of this macro for non-ASCII characters currently depends on the player's browser's Unicode support. For instance, 'İ' in lowercase should be 'i̇', but some browsers don't support this.
See also:
(uppercase:), (lowerfirst:), (upperfirst:)
The (lowerfirst: ) macro
(lowerfirst: String) → String
This macro produces a version of the given string, where the first alphanumeric character is lowercase, and other characters are left as-is.
Example usage:
(lowerfirst: " College B")
is the same as " college B"
Details:
If the first alphanumeric character cannot change case (for instance, if it's a number) then nothing will change in the string. So, "8DX" won't become "8dX".
The results of this macro for non-ASCII characters currently depends on the player's browser's Unicode support. For instance, 'İ' in lowercase should be 'i̇', but some browsers don't support this.
See also:
(uppercase:), (lowercase:), (upperfirst:)
The (plural: ) macro
(plural: Number, String, [String]) → String
This macro takes a whole number and a string, then converts the number to a string, joins them up with a space character, and pluralises the string if the number wasn't 1 or -1. By default, this pluralisation is done by adding "s", as in some English plurals. An optional extra string can specify a different plural word to use instead.
Example usage:
(plural: 1, "bandage")
produces"1 bandage"
.(plural: -7, "bandage")
produces"-7 bandages"
.(plural: 2, "elf", "elves")
produces"2 elves"
.
Rationale:
If you have variables in your story holding number data, you'll often want to display that data to the player textually. If that number refers to a quantity of some object or substance, and your game is in English, you'll want to pluralise the noun form of that object or substance, which requires checking if the number is or is not 1 or -1. This macro is a shortcut for that small bit of busywork, letting you simply supply the number and the noun to produce the plural.
Details:
If the number isn't a whole number (such as 2.3), then an error will result. Furthermore, if any of the given strings are empty, an error will result.
See also:
The (source: ) macro
(source: Any) → String
When given almost any data value, this will produce a string representation of Harlowe source code that can, when run, create that value exactly.
Example usage:
(source: $voltage)
will, if $voltage contains the number 9, produce the string"9"
.(source: (str-repeated: 6, "HA"))
produces the string'"HAHAHAHAHAHA"'
(which, you'll notice, is a string in a string).(source: (click: ?hat))
produces the string"(click:?hat)"
.(source: (enchant: ?passage's hooks, $style))
will, if $style contained(text-size:1.4)
, produce the string"(enchant:?passage's hooks,(text-size:1.4))"
.
Rationale:
Throughout development, you'll often find yourself toying and tinkering with the exact values of data your story uses, such as to test a particular state of events, or to extract a particular procedurally-generated value. This macro, along with Harlowe's normal code parsing actions, provides a basic two-way conversion between code and data that you can use as you please.
Details:
For most complex values, like changers and commands, this will produce a macro call. The whitespace between the values will
generally be absent, so (source: (a:2, 3, 4))
produces "(a:2,3,4)"
. Also, if you call a macro using one if its aliases,
such as (array:) for (a:), then the source will still use its "default" name. So, (source: (array:1))
produces "(a:1)"
.
Note that you can't easily print the string returned by (source:), because, funnily enough, Harlowe will immediately re-render it. You can use (verbatim-source:) to accomplish this instead.
A special note about commands created by custom macros (via the (output:) macro): as of Harlowe 3.3.0, these can be given to this macro. However,
the representation of this command will be a custom macro call, using the variable name that held the custom macro, at the point the command was created.
What this means is that, for a custom macro stored in $a
, the call ($a:2)
will produce a command whose (source:) representation is "($a:2)""
. But, if
the custom macro's variable is repurposed, such as by (set: $a to 0)
, then the command will still be represented as "($a:2)"
, even though $a no longer
contains the custom macro which created the command. You can generally avoid this issue by keeping custom macros in the same variables for the full duration of
the story.
See also:
(datatype:), (verbatim-source:)
The (split: ) macro
(split: String or Datatype, String) → Array
Also known as: (splitted:)
This splits up the second value given to it into an array of substrings, after finding and removing each occurrence of the first string or pattern (which is used as a separator value).
Example usage:
(split: newline, (passage:"Kitchen")'s source)
produces an array of each line in the "Kitchen" passage's source.(split: (p:",", (p-opt:" ")), "Rhett, Brett, Brad,Red")
produces(a:"Rhett","Brett","Brad","Red")
.
Rationale:
It's common to want to extract substrings from a string, but you often want to do so not based on any fixed number of characters in the string, but on the location of a separator value within the string. For instance, extracting the words from a string, such as with (words:), means you should consider whitespace to be the separator between words. This macro provides a general means of splitting strings based on any separator you wish, using either a substring, a string-related datatype, or a string pattern datatype created with (p:) or its family of macros.
As with most of Harlowe's data-processing macros, the word "split" should be considered an adjective, not a verb - it produces a "split string", not a command to split a string.
Details:
If no occurrences of the separator are found in the string, then an array containing just the complete string (with no splits) is produced.
If the separator (the first value) is the empty string, then the second string will be simply split into an array of its characters, as if by (a: ...$secondValue)
.
If the separator is a pattern that matches the entire string (such as (split: "Hairs", "Hairs")
or (split: string, "Gadfly")
,
then an array containing just the empty string will be produced.
The pattern given to this macro cannot contained TypedVars (such as (split: (p: alnum-type _letter), "A")
). Doing so will cause an error.
See also:
(words:), (folded:), (joined:), (trimmed:), (str-replaced:)
The (str-find: ) macro
(str-find: Datatype, String) → Array
Also known as: (string-find:)
When given a string pattern and a string, this produces an array of each substring within the string that the pattern matched. If typed variables were in the pattern, this instead produces an array of datamaps, each of which has data names matching the variables, and data values matching the portions of the string matched by those typed variables, as well as a "match" data name for the full substring.
Example usage:
(str-find: digit, "PARSEC47")
produces(a:"4","7")
.(str-find: (p:"S", ...alnum), "Mr. Smith, Mr. Schmitt, and Mr. Smithers")
produces(a:"Smith","Schmitt","Smithers")
.(str-find:(p:"$", ...digit, ".", ...digit), "Apple pie - $5.50; Pumpkin pie - $14.50")
produces(a:"$5.50","$14.50")
.(str-find:(p:...alnum-type _flavor, " pie - $", (p:...digit, ".", ...digit)-type _cost), "Apple pie - $5.50; Pumpkin pie - $14.50")
produces the following:
(a: (dm:"cost","5.50","flavor","Apple","match","Apple pie - $5.50"), (dm:"cost","14.50","flavor","Pumpkin","match","Pumpkin pie - $14.50") )
Rationale:
The matches
operator allows you to check if a string contains a specific substring that matches a pattern. This macro goes further, leting you extract every instance
of those matching substrings, and use them as data. This is very useful if you have a structured string that you'd like to "break down" by removing unimportant
parts, but which isn't uniform enough to simply use (split:) for.
Using typed variables to extract multiple components of the substring at once, such as in the above example, can let you directly translate a string
into a sequence of data structures that provide easy access to that data. Rather than having to perform additional (str-find:) calls on the results of the first
(str-find:), you can tag parts of the initial pattern with -type
and a temp variable name, thus causing a datamap of those parts to be created. This array of
datamaps can then be tweaked further with (altered:). If the last (str-find:) example above was stored in the variable _pies
, then one could write
(altered: via its cost + " for " + (lowercase:its flavor), ..._pies)
to create (a: "5.50 for apple", "14.50 for pumpkin")
.
Regarding (find:) and (str-find:):
You might wonder how this compares to (find:), and why the latter only takes a lambda instead of a pattern (such as that given to (unpack:)).
Here is how to think of the two macros. The (find:) macro operates on sequences of values, and treats all of them as discrete values whose order doesn't
impact their individual meaning. Hence, it takes a where
lambda that checks each value individually. Strings, however, consist of sequences of characters that
only have meaning from their order, so (str-find:) takes a string pattern that can match a long substring, or various substring possibilities, by itself.
Details:
This macro only takes a string pattern as its first value. This is because, if a string was given, every result in the produced array would simply be a copy of that string, which isn't particularly useful.
Here is a more detailed description of how (str-find:) works when the pattern contains typed variables. Whenever (str-find:) encounters a substring, it takes the substring, and "unpacks" it into the typed variables in the pattern (in a manner similar to (unpack:)). Then, it immediately converts these temp variables into datamap names and values, and produces a datamap corresponding to that particular substring. Finally, a "match" dataname is added holding the full substring.
Thus, even though this macro can use typed temp variables in its pattern, this does not cause any temp variables to be created in the passage or containing hook. Other macro calls can't access the temp variables used here, and they essentially do not exist. This is similar to (str-replaced:)'s use of temp variables in its patterns, which also does not affect the temp variables in the passage or containing hook.
If no matches of the pattern exist within the string, an empty array is returned.
If two identical TypedVar names are used in the pattern (such as (p: alnum-type _a, alnum-type _a)
) then an error will occur.
If a TypedVar named _match
is used, then an error will occur (because this collides with the "match" dataname containing the full substring).
See also:
(substring:), (count:), (split:), (str-replaced:)
The (str-nth: ) macro
(str-nth: Number) → String
Also known as: (string-nth:)
This macro takes a whole number, and converts it to a string comprising an English ordinal abbreviation (of the form "nth", such as "1st", "22nd", etc.).
Example usage:
(str-nth: 3)
produces"3rd"
.(str-nth: 11)
produces"11th"
.(str-nth: 0)
produces"0th"
.(str-nth: -7)
produces"-7th"
.
Rationale:
English ordinals are useful to express that a number refers to a position or ordering of some object or item, but constructing an ordinal word from a number can be tricky, given that English ordinals have special cases for numbers ending in 1 or 2. This macro, then, serves to smooth over those cases, and provide a succinct means to construct these words.
Details:
Do not confuse this with the (nth:) macro, which is primarily used to display values in a sequence in passage prose.
If the number isn't a whole number (such as 2.3), then an error will result.
Note that you do NOT need to use this to access array data positions, even though their positions are written in the form
1st
, 2ndlast
and so forth. You can simply use numbers in brackets (such as $inventoryArray's (2)
) to access a particular data value.
See also:
The (str-repeated: ) macro
(str-repeated: Number, String) → String
Also known as: (string-repeated:)
A special shorthand combination of the (str:) and (repeated:) macros, this accepts a single string and duplicates it the given number of times.
Example usage:
(str-repeated: 5, "Fool! ")
produces"Fool! Fool! Fool! Fool! Fool! "
Rationale:
This macro is a shorthand form of nesting (repeated:) inside (str:). This example:
(str: ...(repeated: 14, "-+*+"))
is the same as (str-repeated: 14, "-+*+")
.
Details:
An error will, of course, be produced if the number given is negative, or contains a fraction. As of 3.2.0, however, it will no longer error if the number is 0.
See also:
The (str-replaced: ) macro
(str-replaced: [Number], String or Datatype, String or Lambda, String) → String
Also known as: (string-replaced:), (replaced:)
This macro produces a copy of the content string (the last string given), with all instances of a certain search string (or pattern) replaced using a given string (or a "via" lambda that constructs a replacement string). Giving an optional number N lets you only replace the first N instances. If a pattern is given, TypedVars may be used inside it, and they will be accessible in the "via" lambda.
Example usage:
(str-replaced: "http://", 'https://', $URL)
produces a copy of the string in $URL with all occurrences of "http://" replaced with "https://".(str-replaced: (p-many: whitespace), ' ', $playerName)
produces a copy of the string in $playerName with all runs of whitespace (including multiple spaces) replaced with a single space.(str-replaced: 1, '*', '☆', _nextLine)
produces a copy of _nextLine with the first occurrence of "*" replaced with "☆".(str-replaced: (p-many:alnum), via (str-reversed: it), "09-4128-3253")
produces"90-8214-3523"
.(str-replaced: (p: (p-many:digit)-type _a, "%"), via _a + " percent", "5% 10%")
produces"5 percent 10 percent"
.
Rationale:
This macro accompanies (str-find:) - in addition to finding matches of a given pattern, this also lets you replace them there and then, producing a new version of the string. This can serve a variety of uses. Player-inputted text, such as by (input-box:), will often need to have unwanted characters (such as newlines) removed. Strings used for procedurally describing certain objects or game states, like the player's physical status, may need to be subtly modified to fit in specific sentences. Or, you may want to do something more esoteric, like "corrupting" or "censoring" a string by replacing certain characters arbitrarily.
While you can replace text that's already been rendered into the passage using (replace:) or (replace-with:), this macro is better suited for modifying strings that are stored in variables and used throughout the story.
Details:
While this macro does have the shorter alias as (replaced:), it is recommended that (str-replaced:) be used to avoid confusion with the (replace:) macro, which is a changer that performs immediate replacements (using the attached hook) on passage hooks and text, not strings.
Matches and replacements are non-overlapping - if the search string or pattern would overlap certain locations in the string, only the leftmost location is replaced.
So, (str-replaced: "1010", 'X', "101010")
will produce "X10"
, not "10X"
or "XX"
.
You can supply a "via" lambda to specify a more complicated replacement than just a single string. The it
identifier in the lambda will be the matched portion of the string.
In the case of (str-replaced: alnum, via it + "-", "Fred...?")
, it
will be "F", "r", "e", and "d", respectively.
Of course, as always, you can provide a temp variable in front of the "via" keyword, which will be usable in the lambda as a more readable alternative to the it
identifier.
In the case of (str-replaced: (p:(p-many:digit), "-", (p-many:digit)), _phoneNum via "(131) " + _phoneNum, "Call me on 999-000!")
, both _phoneNum and it
will be "999-000".
Interestingly, any pattern (using the (p:) macro and its relatives) given to (str-replaced:) can have TypedVars inside it. These TypedVars will be accessible within the "via" lambda.
In the case of (str-replaced: (p: (p-many:digit)-type _a, "%"), via _a + " percent", "5% 10%")
, above, the "via" lambda will be run twice: the first time, it
will be "5%", and _a will be "5"
(because it contains only the (p-many:digit)
portion of the matched string). The second time, it
will be "10%", and _a will be "10". This feature allows you to
capture "sub-groups" within the matched sub-string, and use them in the "via" lambda to make a custom replacement for every match in the content string.
By the way, the pos
identifier can also be used inside the "via" lambda. It equals the number of replacements before and including this one.
Here's a short table summarising some of the above information.
Macro call | it |
pos |
_a |
Replacement made by the lambda |
---|---|---|---|---|
(str-replaced: (p: (p-many:digit)-type _a, "%"), via _a + " percent", "5% 10%") |
"5%" |
1 |
"5" |
"5 percent" |
"10%" |
2 |
"10" |
"10 percent" |
|
(str-replaced: alnum, via it + "-", "Fred...?") |
"F" |
1 |
n/a | "F-" |
"r" |
2 |
n/a | "r-" |
|
"e" |
3 |
n/a | "e-" |
|
"d" |
4 |
n/a | "d-" |
The TypedVars used in the pattern are only usable in the lambda. They essentially do not exist outside of the macro call, and will not be accessible to macros elsewhere in the passage.
The optional number decides how many replacements, starting from the left of the content string, should be made. If no string is given, all possible replacements are made. If 0 is given, no replacements will be made, the content string will be returned as-is, and no error will occur. Giving negative numbers or non-whole numbers will produce an error, though.
If an empty or meaningless pattern is given as a search value (for instance, (p:)
or ""
) then no replacements will be made and the content string will be returned as-is.
See also:
(trimmed:), (split:), (unpack:)
The (str-reversed: ) macro
(str-reversed: String) → String
Also known as: (string-reversed:)
A special shorthand combination of the (str:) and (reversed:) macros, this accepts a single string and reverses it.
Example usage:
(str-reversed: "sknahT")
produces"Thanks"
Rationale:
This macro is a shorthand form of nesting (reversed:) inside (str:). This example:
(str: ...(reversed: "ABRAXAS"))
is the same as (str-reversed: "ABRAXAS")
.
Details:
This accepts strings of 0 or 1 character, as well as symmetrical strings, even though their "reversal" is the same as their current form.
If you wish to reverse just the words in a string, you can use the ordinary (reversed:) and (words:) macros
like so: (reversed: ...(words: "Gilly Golly Milly Molly"))
.
See also:
The (substring: ) macro
(substring: String, Number, Number) → String
This macro produces a substring of the given string, cut from two inclusive number positions.
Example usage:
(substring: "growl", 3, 5)
is the same as "growl"'s 3rdto5th
or "growl"'s (a:3,4,5)
Rationale:
You can obtain substrings of strings without this macro, by using the 's
or of
syntax along
with either a specified range of consecutive positions, or an array of arbitrary position numbers.
For instance, $str's 4thto12th
obtains a substring of $str containing
its 4th through 12th characters, $a's (a:1,3,5)
obtains a substring of just the 1st, 3rd and 5th characters of $a,
and $a's (range:1, $b)
obtains a substring of each position up to $b.
However, in the specific situation where you want to use a variable negative position, counting from the end of the string,
there isn't a succinct option using that syntax. When gathering the characters in string $a
between position 1 and $b, where $b is a negative position counting from the end, (range:1, $b)
doesn't work, and
the best you can do without this macro is something like $a's (range: 1, $b + $a's length)
. So, this
macro can be used as a slightly shorter alternative, by writing (substring: $a, 1, -$b)
.
Details:
As mentioned above, if you provide negative numbers, they will be treated as being offset from the end
of the string - -2
will specify the 2ndlast
character, just as 2 will specify
the 2nd
character.
If the last number given is smaller than the first (for instance, in (substring: "hewed", 4, 2)
)
then the macro will still work - in that case returning "ewe" as if the numbers were in
the correct order.
See also:
The (trimmed: ) macro
(trimmed: [String or Datatype], String) → String
This macro takes one string (the last value), and produces a copy with every character matching the given pattern (the first value) removed from the start and end of it. If no pattern
is given, it defaults to removing whitespace, as if whitespace
was the first argument.
Example usage:
(trimmed:" Contract Annulled ")
produces"Contract Annulled"
.(trimmed: "$", $treasureValue)
produces the string stored in $treasureValue with leading or trailing "$" signs removed.(trimmed: digit, "john61112")
produces"john"
.(trimmed: (p-start: whitespace), _text)
trims off whitespace from the start of the string in _text, but not the end.
Rationale:
Removing certain leading or trailing characters in a string is a common operation, and is essentially equivalent to extracting a single substring from within a string. Removing the punctuation or whitespace surrounding a word, or just certain specific characters, is important when you need to use the middle portion of a string for some other use, such as being displayed in a different context. It's especially useful when dealing with user-inputted strings, such as those produced by (input-box:).
Details:
If an empty string is given, then it will be returned as-is. If the pattern doesn't match anything (for instance, if just (p:)
or "" was given as the pattern)
then the string will be returned as-is.
If the pattern matches the entire string, then an empty string will be returned.
The pattern given to this macro cannot contained TypedVars (such as (split: (p: alnum-type _letter), "A")
). Doing so will cause an error.
See also:
(words:), (split:), (str-replaced:)
The (uppercase: ) macro
(uppercase: String) → String
This macro produces an uppercase version of the given string.
Example usage:
(uppercase: "GrImAcE")
is the same as "GRIMACE"
Details:
The results of this macro for non-ASCII characters currently depends on the player's browser's Unicode support. For instance, 'ß' in uppercase should be 'SS', but some browsers don't support this.
See also:
(lowercase:), (upperfirst:), (lowerfirst:)
The (upperfirst: ) macro
(upperfirst: String) → String
This macro produces a version of the given string, where the first alphanumeric character is uppercase, and other characters are left as-is.
Example usage:
(upperfirst: " college B")
is the same as " College B"
Details:
If the first alphanumeric character cannot change case (for instance, if it's a number) then nothing will change in the string. So, "4ever" won't become "4Ever".
The results of this macro for non-ASCII characters currently depends on the player's browser's Unicode support. For instance, 'ß' in uppercase should be 'SS', but some browsers don't support this.
See also:
(uppercase:), (lowercase:), (lowerfirst:)
The (words: ) macro
(words: String) → Array
This macro takes a string and creates an array of each word ("word" meaning a sequence of non-whitespace characters) in the string.
Example usage:
(words: "god-king Torment's peril")
is the same as (a: "god-king", "Torment's", "peril")
Rationale:
It can be useful to explicitly distinguish individual words within a string, in a manner not possible
with just the contains
operator - for instance, seeing if a string contains the bare word "to" - not "torn"
or any other larger word. This macro allows a string's words to be split up and examined individually -
you can safely check if (words: $a) contains "to"
, or check on a particular word in the sequence by
asking if, say, (words: $a)'s 2nd is 'goose'
.
Details:
If the string was empty or contained only whitespace, then this will create an empty array. Moreover, if the string contained no whitespace, then the array will contain just the entire original string.
If you wish to split up a string into an array based on a more specific separator than just whitespace (for instance, by just newlines) then you may use the (split:) macro.
See also:
(split:), (startcase:), (trimmed:)
The (align: ) macro
(align: String) → Changer
This styling changer changes the alignment of text in the attached hook, as if the
===>
arrow syntax was used. In fact, these same arrows (==>
, , =><=
<==>
, etc.)
should be supplied as a string to specify the degree of alignment.====><=
Example usage:
(align: "=><==")[Hmm? Anything the matter?]
Details:
Hooks affected by this changer will take up their own lines in the passage, regardless of their placement in the story prose. This allows them to be aligned in the specified manner.
The (bg: ) macro
(bg: Colour or String or Gradient) → Changer
Also known as: (background:)
This styling changer alters the background colour or background image
of the attached hook. Supplying a gradient (produced by (gradient:)) will set the
background to that gradient. Supplying a colour (produced by (rgb:), (hsl:), (lch:) or another such macro),
a built-in colour value like red
, or a bare colour value like #FA9138) will set
the background to a flat colour. CSS strings that resemble HTML hex colours (like "#FA9138") will
also provide flat colour. Other strings will be interpreted as an image URL,
and the background will be set to it.
Example usage:
(bg: red + white)[Pink background]
(bg: (gradient: 0, 0,red, 1,black))[Red-black gradient background]
(bg: #663399)[Purple background]
(bg: "#663399")[Purple background]
(bg: "marble.png")[Marble texture background]
Details:
Combining two (bg:) changers will do nothing if they both influence the
colour or the image. For instance (bg:red) + (bg:white)
will simply
produce the equivalent (bg:white)
. However, (bg:red) + (bg:"mottled.png")
will work as intended if the background image contains transparency, allowing the background
colour to appear through it. Note that gradients count as background images, not colours - you can
combine gradients whose colours are partially transparent with flat colours, such as
(bg: (gradient: 90, 0, (hsla:0,0,0,0.5), 1, (hsla:0,0,0,0))) + (bg: red)
Currently, supplying other CSS colour names (such as burlywood
) is not
permitted - they will be interpreted as image URLs regardless.
No error will be reported if the image at the given URL cannot be accessed.
See also:
The (box: ) macro
(box: String, [Number]) → Changer
When attached to a hook, it becomes a "box", with a given width proportional to the containing element's width, an optional number of lines tall, and a scroll bar if its contained text is longer than its height can contain.
Example usage:
(box:"=XX=", 1)[Chapter complete]
produces a box that's centered, 50% of the containing element's width, and 1 line tall.(box:"==X", 3)[Chapter complete]
produces a box that's right-aligned, 33% of the containing element's width, 3 lines tall.(box:"X", 7)[Chapter complete]
produces a box that takes up the full containing element's width, and 7 lines tall.(enchant: ?passage, (box:"XXX="))
enchants the passage, placing it in the left of the window.
Rationale:
There are times when you want to make a block of text appear to occupy an enclosed, small region with its own scroll bar, so that players can scroll through it separate from the rest of the passage - for instance, if it's an excerpt of some in-story document, or if it's a "message log" which has lots of text appended to it with (append:). This macro provides that ability.
Details:
The first value you give to this macro is a "sizing line" similar to the aligner and column markup - a sequence of zero or
more =
signs, then a sequence of characters (preferably "X"), then zero or more =
signs. Think of this string as a visual
depiction of the box's horizontal proportions - the =
signs are the space to the left and right, and the characters in
the middle are the box itself. If you wish to specify that the box should take up the full width, you must provide
just a single character, like "X" - anything more will cause an error.
The second, optional value is a height, in text lines. This size varies based on the font size of the containing element,
which is adjustible with (text-size:) and other changers. The hook will (as of version 3.3.6) be given a CSS height
value of 1.5em
(the default CSS line-height
) multiplied by the number of lines given. If you need to reposition the hook vertically, consider using
(float-box:) instead. Note that this value will NOT adjust to match any custom CSS padding or line-height
given to this hook in addition to (box:).
If no height is given, then it will use a height large enough to display all of the lines, as usual. If a non-whole number is given, an error will be produced.
The "containing element" is whatever structure contains the hook. If it's inside column markup, the containing column is the element. If it's inside another hook (including a hook that also has (box:) attached), that hook is the element. Usually, however, it will just be the passage itself.
This changer does not interact well with (align:), which also sets the horizontal placement of hooks - adding these changers together will cause one to override the placement of the other. (align:) will also, if center-alignment is given, force the hook's horizontal size to 50% of the containing element.
If you want the box's horizontal size to be a large proportion of the available width, it may be more readable if you uniformly varied
the characters that comprise the sizing string: (box:"=XxxxXxxxXxxxX=", 0.25)
, for instance, makes it easier to discern that
the box is 13/15th of the available width.
You can use this with (change:) or (enchant:) and ?passage
to affect the placement of the passage in the page. (Note that doing so will change
the horizontal padding of the <tw-story>
HTML element, which is normally 20%. It will become 0%, and the <tw-passage>
's new margins
will define its position on the screen.)
The resulting hook has the CSS attributes "display:block", "overflow-y:auto", and "box-sizing:content-box". Additionally, the hook will have 'padding:1em', unless another padding value has been applied to it (such as via (css:)).
See also:
The (button: ) macro
(button: [String]) → Changer
When applied to a link, this changer styles it so that it resembles a button, and makes it take up the entire passage width. The optional sizing string lets you specify width and horizontal margins for the button. It is not recommended that this be used on non-link hooks.
Example usage:
(button:)[[Go to the cemetery]]
applies the button style to a single passage link.(button:"=XXX=")[[Hug your husband]]
turns the link into a button that occupies 60% of the available width, centered.(enchant:?link's 2ndlast + ?link's last, (button:))
enchants the second-last and last links in the passage with the button style.
Rationale:
Harlowe links, by default, are designed to appear inside and among prose, in the manner of HTML prose. That being said, a story written in a more traditional interactive fiction style will often want to finish a passage with a series of exit links. These links can benefit from being more visually prominent and inviting, rather than just single fragments of text. The (button:) changer provides links with a styling that is more typical of other interactive fiction engines' link options.
Details:
This is essentially a shortcut for a number of other changers added together. (link: "Link Text", (button:))
is similar to
(link:"Link Text",(align:"=><=")+(box:"X")+(b4r:"solid")+(css:"padding:0px")+(corner-radius:16))
. However, unlike the latter,
this changer is designed to work correctly with (click:) and (enchant:"text")
, so that the button border matches the current link colour.
The optional sizing string is the same kind of line given to (box:) - a sequence of zero or more =
signs, then a sequence of characters (preferably "X"), then zero or
more =
signs. Think of this string as a visual depiction of the button's horizontal proportions - the =
signs are the space to
the left and right, and the characters in the middle are the button itself.
To make (button:) links appear in two or more columns, or make two (button:) links appear side-by-side, consider using the column markup.
This changer can be provided to non-link hooks or commands, but since the result will have the same borders and spacing as a button while not being clickable, it is not recommended to use it this way.
This changer adds the class "enchantment-button" to <tw-link>
and <tw-enchantment>
elements.
See also:
(align:), (border:), (box:), (corner-radius:)
The (char-style: ) macro
(char-style: Changer or Lambda) → Changer
When attached to a hook, this causes all of the individual non-whitespace characters inside the hook (identical to those that would be selected by ?page's chars
) to be styled using the specified changer.
Example usage:
(char-style:(text-style:"fidget"))[Maybe you stayed up too late.]
(char-style: via (text-style:(either:'none','blur','blurrier')))[My memory is patchy. You'll have to fill in the gaps yourself.]
(char-style: via (text-colour:(either:red,white,white)))[Blood? What blood? You're clean as a whistle.]
Rationale:
This is a convenient and more readable shorthand for using (enchant-in:) with ?page's chars
. This lets you style all of the characters within a hook, as if they were in individual hooks themselves.
A number of strange text effects are possible with this - each character can be rotated using (text-rotate-z:), each character can have a (hover-style:), each character can have a slightly different (text-size:),
and so forth.
Details:
As with (enchant-in:), this can be given a changer, or a lambda that produces a changer, which is run on each character individually, and can produce different changers for each, depending on
their pos
or a random macro. If the lambda doesn't produce a changer, an error will result.
Also, as with (enchant-in:), (link:), (replace:), or any of their relatives cannot be given to this macro.
This creates a hook-specific enchantment, similar to (enchant-in:), It will be listed under the "Enchantments" tab of the Debug Mode panel.
Warning: using (char-style:) may cause text-to-speech assistive programs to fail to read the hook's contents correctly. If this would be unacceptable for you or your story, refrain from using this macro.
Warning: using (char-style:) to enchant very large amounts of text at once will likely cause excessive CPU load for the reader, making their browser unresponsive.
Due to Harlowe engine limitations, this currently does NOT work when created by a lambda given to (enchant:)
or (change:)
, such as in (enchant: ?passage, via (char-style:(bg:(hsl:pos*30,0.5,1))))
.
See also:
(enchant-in:), (hover-style:), (link-style:), (line-style:)
The (collapse: ) macro
(collapse: ) → Changer
When attached to a hook, this collapses the whitespace within the hook, in the same manner as the collapsing whitespace markup.
Example usage:
(collapse:)[This text is (set:$a to 1) collapsed.]
(enchant: ?page, (collapse:))
Rationale:
While the collapsing whitespace markup allows specific sections of passage prose to be collapsed, there are times when you want this functionality available as a changer, such as to style the whole page using (change:), or to add it to another changer. This macro provides that functionality.
Details:
Unlike most macros, this takes no values - there is only one way of collapsing whitespace (for now).
This collapses whitespace in the same manner as the collapsing whitespace markup, so consult its documentation for more information.
There is no way to reverse this whitespace-collapsing effect - it is permanently removed.
When this is used with (change:) or (enchant:) to affect an existing hook, its excess whitespace will be deleted immediately, with no transition. Moreover, the whitespace-collapsing effect is ongoing, not just a once-off effect. This becomes clear when you consider the following code.
(enchant:?1, (collapse:))
|1>["Back in time? Is this a time travel story now?"]
(append:?1)[
he shook his head.
]
Because the enchantment is an ongoing effect, the text appended to ?1 will be collapsed, even though it's written outside of the collapsing hook. This would not occur if ?1 was a span of collapsing whitespace markup.
The (css: ) macro
(css: String) → Changer
This takes a string of inline CSS, and applies it to the hook, as if it were a HTML "style" property.
Example usage:
(css: "background-color:indigo;color:white;")[What's going on? Where am I?]
Rationale:
The built-in macros for layout and styling hooks, such as (text-style:), are powerful and geared toward ease-of-use, but do not entirely provide comprehensive access to the browser's styling. This changer macro allows extended styling, using inline CSS, to be applied to hooks.
This is, however, intended solely as a "macro of last resort" - as it requires basic knowledge of CSS - a separate language distinct from Harlowe - to use, and requires it be provided a single inert string, it's not as accommodating as the other such macros.
See also:
The (float-box: ) macro
(float-box: String, String) → Changer
When attached to a hook, it becomes a "floating box", placed at a given portion of the window, sized proportionally to the window's dimensions, and with a scroll bar if its contained text is longer than its height can contain.
Example usage:
(float-box: "X====","Y====")[CASH: $35,101]
produces a box that's placed in the top-left corner of the window, is 20% of the window's width, and 20% of the window's height.(float-box: "=XXX=","======Y")[Marvin: "Really?"]
produces a box that's placed in the middle bottom of the window, is 60% of the window's width, and 1/7th of the window's height.
Rationale:
This is a variant of (box:). There are times when you want a single block of text to be separated from the main passage's text, to the point where it's positioned offset from it as a separate panel - character statistics readouts in RPGs, and commentary asides are two possible uses. Unlike (box:), which leaves the hook in the passage, this provides that necessary spatial separation.
Details:
The values you give to this macro are "sizing lines" identical to those accepted by (box:) - consult its documentation for more
information about those lines. However, while those lines scaled the hook proportional to the "containing element", (float-box:)
scales proportional to the reader's browser window, using vw
and wh
CSS units. The second string references the vertical
position and size of the hook - since (box:) cannot affect the vertical position of the hook, it only accepts a number representing
its size.
It's a recommended convention that the centre characters in the sizing line strings be "X" (for "X axis") for the horizontal line
and "Y" (for "Y axis") for the vertical - but you may use whatever you wish as long as it is not a =
.
Since it is "floating", this box remains fixed in the window even when the player scrolls up and down.
The resulting hook has the CSS attributes "display:block", "position:fixed" and "overflow-y:auto". Additionally, the hook will have 'padding:1em', unless another padding value has been applied to it (such as via (css:)).
See also:
The (font: ) macro
(font: String) → Changer
This styling changer changes the font used to display the text of the attached hook. Provide the font's family name (such as "Helvetica Neue" or "Courier") as a string.
Example usage:
(font:'Courier New')[And what have we here?]
Details:
Currently, this command will only work if the font is available to the player's browser, or
if font files are linked using url()
in your story's stylesheet, or embedded using base64 (explanations
for which are beyond the scope of this macro's description).
No error will be reported if the provided font name is not available, invalid or misspelled.
See also:
The (hook: ) macro
(hook: String) → Changer
A changer that allows the author to give a hook a computed tag name.
Example usage:
(hook: $name)[]
gives the hook a name equal to what string is in the $name variable.
Rationale:
It's possible to add together changers, save them in variables, and use them in various locations
throughout your story. You may, after doing so, want to give a common name to each of those hooks that
have that variable attached, so that, for instance, the (append:) macro can act on them as one.
This changer can be added to those changers to allow the hooks to be named, like so.
(font:"Museo Slab")+(hook:"title")
.
Also, unlike the nametag syntax for hook names, (hook:) can be given any string expression:
(hook: "eyes" + (str:$eyeCount))
is valid, and will, as you'd expect, give the hook
the name of eyes1
if $eyeCount
is 1.
Details:
If an empty string is given to this macro, an error will be produced.
Currently, you may give strings with non-alphanumeric characters in them, such as "!@#". However, since those characters are not valid for use in HookName syntax, you can't use a HookName to refer to that hook, so it's not that useful.
Hook names are case-insensitive and dash-insensitive. This means that (hook:"BAG")
, (hook:"bag ")
and (hook:"bag")
are all equivalent.
See also:
The (hover-style: ) macro
(hover-style: Changer) → Changer
Given a style-altering changer, it makes a changer which only applies when the hook or command is hovered over with the mouse pointer, and is removed when hovering off.
Example usage:
(enchant:?Link, (hover-style:(text-style:'italic')))
makes each of the page's links turn italic when hovered over.(text-colour:transparent)+(hover-style:(text-color:red))[The butler] killed Marcus O'Fogarty.
makes a hook, whose text is normally transparent, turn white when hovered over.(hover-style:)[]
Rationale:
Making text react in small visual ways when the pointer hovers over it is an old hypertext tradition. It lends a degree of "life" to the text, making it seem aware of the player. This feeling of life is best used to signify interactivity - it seems to invite the player to answer in turn, by clicking. So, adding them to (link:) commands, as well as interaction commands like (cycling-link:), is recommended.
Details:
True to its name, this macro can only be used for subtle style changes. Only the following changers (and combinations thereof) may be given to (hover-style:) - any others will produce an error:
- (align:)
- (bg:)
- (css:)
- (font:)
- (text-colour:)
- (text-indent:)
- (text-rotate-x:)
- (text-rotate-y:)
- (text-rotate-z:)
- (text-style:)
- (text-size:)
More extensive mouse-based interactivity should use the (action:) changer.
This macro is not recommended for use in games or stories intended for use on touch devices, as the concept of "hovering" over an element doesn't really make sense with that input method.
Note that in versions of Harlowe prior to 3.2.0, this could be combined with (link:), (link-repeat:), or (link-reveal:)
to apply changers to the link, except for (text-colour:). This has since been changed, and now, when combined with (link:)
changers, (hover-style:) will only apply to the revealed hook. (The intended way to style the link in that case is to provide (hover-style:) as
the optional second value to a link changer, such as by (link-rerun:"Retry", (hover-style:(color:red)))
.)
Note that (link-goto:)
and passage links aren't changers, so (hover-style:) can be attached to them, as expected.
See also:
(link-style:), (line-style:), (char-style:)
The (line-style: ) macro
(line-style: Changer or Lambda) → Changer
When attached to a hook, this causes all of the lines of prose inside the hook (identical to those that would be selected by ?page's lines
) to be styled using the specified changer.
Example usage:
This gives every line in the attached hook a dotted border. Notice that blank "lines" aren't styled, and are ignored.
(line-style:(b4r:"dotted"))
[Sometimes I think
that I'm losing myself
Other times,
that I never had a self
in the first place.]
This makes each line to take up 50% of the passage width, and every other line in the attached hook be right-aligned.
(line-style: via (box:"=XX=")+(align: (cond: pos is an even, "<==", "==>")))
[Sometimes you feel like
your mind is in one place
and your body is in another.]
Rationale:
This is a convenient and more readable shorthand for using (enchant-in:) with ?page's lines
. This lets you style all of the lines within a hook, as if they were in individual hooks themselves.
This allows you to alter and adjust the amount of text inside the hook without having to manually wrap each line in a hook, or attach a changer, after each alteration.
Details:
A line is any run of non-whitespace text or code between line breaks (or the hook's start and end) - a word-wrapped paragraph of prose is considered a single "line" as a result.
As with (enchant-in:), this can be given a changer, or a lambda that produces a changer, which is run on each line individually, and can produce different changers for each, depending on
their pos
or a random macro. If the lambda doesn't produce a changer, an error will result.
Also, as with (enchant-in:), (link:), (replace:), or any of their relatives cannot be given to this macro.
This creates a hook-specific enchantment, similar to (enchant-in:), It will be listed under the "Enchantments" tab of the Debug Mode panel.
Due to Harlowe engine limitations, this currently does NOT work when created by a lambda given to (enchant:)
or (change:)
, such as in (enchant: ?passage, via (line-style:(bg:(hsl:pos*30,0.5,1))))
.
See also:
(enchant-in:), (hover-style:), (link-style:), (char-style:)
The (link-style: ) macro
(link-style: Changer or Lambda) → Changer
When attached to a hook, this causes all of the links inside the hook to be styled using the specified changer. This is equivalent to using (enchant-in:) with ?link
.
Example usage:
(link-style:(b4r:"dotted"))[I stepped into the (link-reveal:"hall,")[ and the door shut behind me.]]
applies the changer produced by (b4r:) to the (link-reveal:) link.(link-style:via (text-colour:(cond: pos is an even, yellow, aqua)))[You choose: [[A]] [[B]] [[C]] [[D]] [[E]].]
gives the "A", "C" and "E" links an aqua colour, and the other links a yellow colour.
Rationale:
Links, being the primary interactive elements in your stories, need to be visually distinguished from the passage prose surrounding them. Harlowe applies a colour and boldness to links by default, but you'll often want to apply your own styles to links to suit your story. Rather than manually attach a changer holding those styles to every link where it appears, you can instead use this macro to style several links at once.
If you wish to style every link in the passage or story equally, using (enchant:) with ?link
in a "header" or "footer" tagged passage is most effective. But, if you only wish to apply a
style to links in certain sections of a passage, this macro is most effective.
Details:
As with (enchant-in:), this can be given a changer, or a lambda that produces a changer, which is run on each link individually, and can produce different changers for each, depending on
their pos
or a random macro. If the lambda doesn't produce a changer, an error will result.
Also, as with (enchant-in:), (link:), (replace:), (append-with:), or any of its relatives cannot be given to this macro.
This creates a hook-specific enchantment, similar to (enchant-in:), It will be listed under the "Enchantments" tab of the Debug Mode panel.
This will also apply the style changer to (click:) links inside the hook.
Due to Harlowe engine limitations, this currently does NOT work when created by a lambda given to (enchant:)
or (change:)
, such as in (enchant: ?passage, via (link-style:(bg:(hsl:pos*30,0.5,1))))
.
See also:
(enchant-in:), (hover-style:), (line-style:), (char-style:)
The (opacity: ) macro
(opacity: Number) → Changer
This styling changer changes how opaque the attached hook is, using a value from 0 to 1. Reducing the value makes it more transparent. An opacity of 0 makes the hook invisible.
Example usage:
(opacity: 0.5)[You don't think there's (color:green)[a revenant] nearby, do you?]
makes the hook 50% transparent.
Details:
This affects the entire hook, including its background, any borders added by (border:), and so forth. Moreover, this does not override "alpha" opacity values of colours produced by (hsl:), (rgb:) and (lch:) – the multiple transparency effects produced by these will multiplicatively stack with one another.
Each nested usage of (opacity:) also multiplicatively stacks with one another. If two hooks with opacity 0.5 are nested, such as by (opacity:0.5)[(opacity:0.5)[Faded]]
,
then the inner hook will have an opacity equivalent to 0.25. As a consequence of this, you can't use (opacity:) inside a partially transparent hook
to bring the inner hook up to 100% opacity.
Two (text-style:) styles, "fade-in-out" and "opacity", will override this changer if it's affecting the same hook.
See also:
(hsl:), (rgb:), (text-colour:)
The (text-colour: ) macro
(text-colour: String or Colour) → Changer
Also known as: (colour:), (text-color:), (color:)
This styling changer changes the colour used by the text in the attached hook. You can supply either a string with a CSS-style colour (a colour name or RGB number supported by CSS), or a built-in colour object.
Example usage:
(colour: red + white)[Pink]
combines the built-in red and white colours to make pink.
(colour: #696969)[Gray]
uses a CSS-style colour to style the text gray.
Details:
This macro only affects the text colour. To change the text background, call upon the (bg:) macro.
This macro will change the colour of links inside the contained hook, with one exception: using
(change:) to change the entire passage (via ?passage
or ?page
) with (text-colour:)
will NOT affect links. This is to allow you to re-style the entire story without having to lose
the distinct colour of links compared to passage text. You can change the colour of all links using
an explicit (enchant: ?link, (text-colour: $color))
.
Also, while this will change the colour of links inside the contained hook, the hover colour for the link will remain the same. You can alter that colour by styling the links using (hover-style:).
See also:
The (text-indent: ) macro
(text-indent: Number) → Changer
This styling changer causes the attached hook to be indented by the given number of pixels.
Example usage:
(enchant: ?passage's lines, (text-indent:12))
gives each line (paragraph) of the passage an indent of 12 pixels.(text-indent: 24)+(size:1.5)[CHAPTER TWO]
makes just this hook have a leading indent of 24 pixels.
Rationale:
Indentation of initial letters has long been used in typesetting as a means of helping the human eye distinguish paragraphs of text from each other. While you can use line breaks to separate paragraphs, this often takes up an uncomfortable amount of vertical space, and can be unsuitable for long sections of prose. This macro can be used to provide indentation to single hooks, or, using (change:) or (enchant:), to every line in a passage.
Details:
This will place a gap before the first character of the attached hook, even if it isn't at the start of a line.
The given number is the number of CSS pixels to indent the hook by. If it is negative, an error will be produced.
Because this uses the CSS 'text-indent' attribute, hooks using this macro will have their CSS display
attribute
set to inline-block
.
It is recommended that you do NOT use this macro for precisely placing text offset from the left or right of the passage. You will get better results using the (align:) macro, aligner marker, or column markup for this purpose.
See also:
The (text-rotate-x: ) macro
(text-rotate-x: Number) → Changer
This styling changer visually rotates the attached hook clockwise, around the X axis (horizontal), by a given number of degrees, making it appear to lean into the page. The rotational axis is in the centre of the hook.
Example usage:
(text-rotate-x:-45)[You feel a strange
lightness, as if you're
in an elevator that's
suddenly started
plunging rapidly.]
Details:
The surrounding non-rotated text will behave as if the rotated text is still in its original position - the horizontal space of its original length will be preserved, and text it overlaps with vertically will ignore it.
A rotation of 90 degrees will, due to the rotational axis, cause the hook to disappear, appearing edge-on to the viewer.
A rotation of 180 degrees will, due to the rotational axis, flip the hook upside-down, as if (text-style:"upside-down")
was applied.
Due to browser limitations, hooks using this macro will have its CSS display
attribute
set to inline-block
.
See also:
(text-style:), (text-rotate-y:), (text-rotate-z:)
The (text-rotate-y: ) macro
(text-rotate-y: Number) → Changer
This styling changer visually rotates the attached hook clockwise, around the Y axis (vertical), by a given number of degrees, making it appear to lean into the page. The rotational axis is in the centre of the hook.
Example usage:
(text-rotate-y:45)+(size:1.5)[ATE BREAKFAST!
READ THE NEWS!
FOUND A LOST SOCK!]
Details:
The surrounding non-rotated text will behave as if the rotated text is still in its original position - the horizontal space of its original length will be preserved, and text it overlaps with vertically will ignore it.
A rotation of 90 degrees will, due to the rotational axis, cause the hook to disappear, appearing edge-on to the viewer.
A rotation of 180 degrees willreverse the hook, as if (text-style:"mirror")
was applied.
Due to browser limitations, hooks using this macro will have its CSS display
attribute
set to inline-block
.
See also:
(text-style:), (text-rotate-z:), (text-rotate-x:)
The (text-rotate-z: ) macro
(text-rotate-z: Number) → Changer
Also known as: (text-rotate:)
This styling changer visually rotates the attached hook clockwise by a given number of degrees. The rotational axis is in the centre of the hook.
Example usage:
(text-rotate:45)[Tilted]
will produce Tilted
Details:
The surrounding non-rotated text will behave as if the rotated text is still in its original position - the horizontal space of its original length will be preserved, and text it overlaps with vertically will ignore it.
A rotation of 180 degrees will, due to the rotational axis, flip the hook upside-down and back-to-front, as if the (text-style:) styles "mirror" and "upside-down" were both applied.
Due to browser limitations, hooks using this macro will have its CSS display
attribute
set to inline-block
.
See also:
(text-style:), (text-rotate-y:), (text-rotate-x:)
The (text-size: ) macro
(text-size: Number) → Changer
Also known as: (size:)
This styling changer changes the text size of the attached hook by the given fraction. Give it a number greater than 1 to enlarge the text, and a number smaller to decrease the text. Providing 1 to this macro will revert the text size back to the default.
Example usage:
This is normal text.
(text-size:0.5)[Umm... this text is half the size of normal text]
(size:2)[This text is enlarged twofold!]
Details:
The default text size for Harlowe, with no other CSS changes to any elements, is 16px (16 pixels), and its default line height is 24px. This macro multiplies both of those CSS properties by the given number, scaling both proportionally. This size is absolute - any pure CSS alterations to the text size of the passage, story or page, using (css:) or story stylesheets, will NOT be taken into account.
This macro also scales any markup which displays text larger or smaller by default, such as header markup or the "superscript" (text-style:).
Be careful about using this macro with (hover-style:) - changing the displayed size of the "hover region" when the mouse begins to hover over it can lead to the pointer "slipping off" the region, causing it to abruptly stop hovering (and deactivating the style) unexpectedly.
See also:
The (text-style: ) macro
(text-style: ...String) → Changer
This applies one or more selected built-in text styles to the hook's text. Give this macro one of these strings (capitalisation and hyphens ignored):
"none"
, "bold"
, "italic"
, "underline"
, "double-underline"
, "wavy-underline"
, "strike"
, "double-strike"
, "wavy-strike"
, "superscript"
,
"subscript"
, "blink"
, "shudder"
, "mark"
, "condense"
, "expand"
, "outline"
, "shadow"
, "emboss"
, "smear"
, "blur"
, "blurrier"
,
"mirror"
, "upside-down"
, "tall"
, "flat"
, "fade-in-out"
, "rumble"
, "sway"
, "buoy"
or "fidget"
.
Example usage:
The shadow (text-style: "shadow")[flares] at you!
will style the word "flares" with a shadow.(set: $s to (text-style: "shadow")) The shadow $s[flares] at you!
will also style it with a shadow.(text-style: "italic", "emboss")[Richard Donahue, King for Hire]
makes the text italic and embossed.
Rationale:
While Harlowe offers markup for common formatting styles like bold and italic, having these styles available from a changer macro provides some extra benefits: it's possible, as with all such style macros, to (set:) them into a variable, combine them with other changers, and re-use them succinctly throughout the story (by using the variable in place of the macro).
Furthermore, this macro also offers many less common but equally desirable styles to the author, which are otherwise unavailable or difficult to produce.
Details:
At present, the following text strings will produce a particular style. All of these are case-insensitive and dash-insensitive - "UPSIDE-DOWN" and "upsidedown" both work in place of "upside-down".
String | Example | Incompatible with |
---|---|---|
"none" |
||
"bold" |
||
"italic" |
||
"underline" |
"double-underline", "wavy-underline", "strike", "double-strike", "wavy-strike" | |
"double-underline" |
"underline", "wavy-underline","strike", "double-strike", "wavy-strike" | |
"wavy-underline" |
"underline", "double-underline", "strike", "double-strike", "wavy-strike" | |
"strike" |
"underline", "double-underline", "wavy-underline", "double-strike", "wavy-strike" | |
"double-strike" |
"underline", "double-underline", "wavy-underline", "strike", "wavy-strike" | |
"wavy-strike" |
"underline", "double-underline", "wavy-underline", "strike", "double-strike" | |
"superscript" |
"subscript" | |
"subscript" |
"superscript" | |
"mark" |
(bg:) | |
"outline" |
"shadow", "emboss", "blur", blurrier", "smear" | |
"shadow" |
"outline", "emboss", "blur", "blurrier", "smear" | |
"emboss" |
"outline", "shadow", "blur", "blurrier", "smear" | |
"condense" |
"expand" | |
"expand" |
"condense" | |
"blur" |
"outline", "shadow", "emboss", "blurrier", "smear" | |
"blurrier" |
"outline", "shadow", "emboss", "blur", "smear" | |
"smear" |
"outline", "shadow", "emboss", "blur", "blurrier" | |
"mirror" |
"upside-down", "tall", "flat" | |
"upside-down" |
"mirror", "tall", "flat" | |
"tall" |
"mirror", "upside-down", "flat" | |
"flat" |
"mirror", "upside-down", "tall" | |
"blink" |
"fade-in-out", "rumble", "shudder", "sway", "buoy", "fidget", (opacity:) | |
"fade-in-out" |
"blink", "rumble", "shudder", "sway", "buoy", "fidget", (opacity:) | |
"rumble" |
"fade-in-out", "blink", "sway", "fidget" | |
"shudder" |
"fade-in-out", "blink", "buoy", "fidget" | |
"sway" |
"fade-in-out", "blink", "rumble", "buoy", "fidget" | |
"buoy" |
"fade-in-out", "blink", "shudder", "sway", "fidget" | |
"fidget" |
"fade-in-out", "blink", "rumble", "shudder", "sway", "buoy" |
You can use the "none" style to remove an existing style from a combined changer. NOTE: As of Harlowe 3.2.2,
this can only be used to remove styles from combined changers, such as by (set: $changer to it + (text-style:"none"))
,
and can't be used to remove styles from already-changed hooks or other structures.
Due to browser limitations, combining many of these changers won't work exactly as intended – (text-style: "underline", "strike")
, for instance,
will cause only the latter of the two to be applied, in this case "strike". These incompatibilities are listed in the table above.
Also due to browser limitations, hooks using "mirror", "upside-down", "tall", or "flat" will have their CSS display
attribute set to inline-block
. This means, among other
things, that the text inside them won't word-wrap.
Note that the animations "rumble" and "shudder" are particularly intense, and may induce frustration or illness in motion-sensitive readers. Take care when using them.
Finally, "doublestrike" and "wavy-strike" will be replaced with "strike" when run on Internet Explorer, as will "double-underline" and "wavy-underline" be replaced with "underline".
See also:
The (transition: ) macro
(transition: String) → Changer
Also known as: (t8n:)
A changer that applies a built-in CSS transition to a hook as it appears. Give this macro one of these strings (capitalisation and hyphens ignored):
"instant"
, "dissolve"
, "fade"
, "rumble"
, "shudder"
, "pulse"
, "zoom"
, "flicker"
, "slide-left"
, "slide-right"
, "slide-up"
, "slide-down"
,
"fade-left"
, "fade-right"
, "fade-up"
and "fade-down"
.
Example usage:
(t8n: "pulse")[Gleep!]
makes the hook [Gleep!]
use the "pulse" transition
when it appears.
Details:
At present, the following text strings will produce a particular transition:
- "instant" (causes the hook to instantly appear)
- "dissolve" or "fade" (causes the hook to gently fade in)
- "flicker" (causes the hook to roughly flicker in - don't use with a long (transition-time:))
- "shudder" (causes the hook to instantly appear while shaking back and forth)
- "rumble" (causes the hook to instantly appear while shaking up and down)
- "slide-right" (causes the hook to slide in from left to right)
- "slide-left" (causes the hook to slide in from right to left)
- "slide-up" (causes the hook to slide in from bottom to top)
- "slide-down" (causes the hook to slide in from top to bottom)
- "fade-right" (causes the hook to gently fade in while sliding rightward)
- "fade-left" (causes the hook to gently fade in while sliding leftward)
- "fade-up" (causes the hook to gently fade in while sliding upward)
- "fade-down" (causes the hook to gently fade in while sliding downward)
- "pulse" (causes the hook to instantly appear while pulsating rapidly)
- "zoom" (causes the hook to scale up from the mouse pointer)
- "blur" (causes the hook to appear from a blur)
All transitions are 0.8 seconds long, unless a (transition-time:) changer is added to the changer.
You can't combine transitions by adding them together, like you can with (text-style:) -
(t8n:"dissolve")+(t8n:"shudder")
won't make a transition that simultaneously dissolve-fades and shudders.
While you can attach this to (link-show:) to change the transitions it uses, you can't use this macro to change the passage transitions used by links or (link-goto:), and trying to do so will cause an error. Please use (transition-depart:) or (transition-arrive:) for this purpose instead.
The "blur" transition will not work in Internet Explorer 10 or 11.
See also:
(text-style:), (transition-time:), (transition-delay:), (transition-skip:), (animate:)
The (transition-delay: ) macro
(transition-delay: Number) → Changer
Also known as: (t8n-delay:)
A changer that, when added to a (transition:) changer, delays the start of the transition by a given time.
Example usage:
(t8n:"slide-right")+(t8n-delay:3s)[Sorry I'm late.]
makes the text slide in from the right, but only after 3 seconds have passed.(char-style: via (t8n-delay:pos*60)+(t8n:'dissolve'))[A pleasure to make your acquaintance.]
causes each character in the hook to appear one-by-one in a 'typewriter' effect.
Details:
Much like (live:) and (after:), this macro should be given a number of milliseconds (such as 50ms
) or seconds
(such as 10s
). Providing negative seconds/milliseconds is not permitted and will result in an error.
Unlike (transition-time:), this does nothing when attached to links, because clicking the link should begin the transition immediately. Attaching it to a link will not produce an error.
See also:
(transition:), (transition-time:), (transition-skip:)
The (transition-time: ) macro
(transition-time: Number) → Changer
Also known as: (t8n-time:)
A changer that, when added to a (transition:) changer, adjusts the time of the transition.
Example usage:
(set: $slowTransition to (transition:"shudder") + (transition-time: 2s))
creates a transition
style which uses "shudder" and takes 2 seconds.
Details:
Much like (live:), this macro should be given a number of milliseconds (such as 50ms
) or seconds
(such as 10s
). Providing 0 or fewer seconds/milliseconds is not permitted and will result in an error.
This can be attached to links, much like (t8n:) itself.
See also:
The (transition-depart: ) macro
(transition-depart: String) → Changer
Also known as: (t8n-depart:)
A changer that alters passage links, (link-goto:)s, and most every other kind of link, changing which passage fade-out animation the link uses.
Example usage:
(t8n-depart: "dissolve")[[Next morning]]
changes the[[Next morning]]
link, such that clicking it takes you to the "Next morning" passage with the current passage smoothly fading out instead of instantly disappearing.(enchant: ?Link, (t8n-depart: "dissolve"))
causes ALL passage links to use the smooth fade-out. This is best used in a "header" or "footer" tagged passage.
Details:
This macro accepts the exact same transition names as (transition:).
- "instant" (causes the passage to instantly vanish)
- "dissolve" or "fade" (causes the passage to gently fade out)
- "flicker" (causes the passage to roughly flicker in - don't use with a long (transition-time:)))
- "shudder" (causes the passage to disappear while shaking back and forth)
- "rumble" (causes the passage to instantly appear while shaking up and down)
- "slide-right" (causes the passage to slide out toward the right)
- "slide-left" (causes the passage to slide out toward the left)
- "slide-up" (causes the passage to slide out toward the top)
- "slide-down" (causes the passage to slide out toward the bottom)
- "fade-right" (causes the passage to gently fade out while sliding rightward)
- "fade-left" (causes the passage to gently fade out while sliding leftward)
- "fade-up" (causes the passage to gently fade out while sliding upward)
- "fade-down" (causes the passage to gently fade out while sliding downward)
- "pulse" (causes the passage to disappear while pulsating rapidly)
- "zoom" (causes the passage to shrink down toward the mouse pointer)
- "blur" (causes the passage to vanish into a blur)
Attaching this macro to a hook that isn't a passage link won't do anything (no error message will be produced).
You can't combine transitions by adding them together, like you can with (text-style:) -
(t8n-depart:"dissolve")+(t8n-depart:"shudder")
won't make a transition that simultaneously dissolve-fades and shudders.
The "blur" transition will not work in Internet Explorer 10 or 11.
See also:
The (transition-arrive: ) macro
(transition-arrive: String) → Changer
Also known as: (t8n-arrive:)
A changer that alters passage links, (link-goto:)s, and most every other kind of link, changing which passage fade-in animation the link uses.
Example usage:
(t8n-arrive: "instant")[[Next morning]]
changes the[[Next morning]]
link, such that clicking it takes you to the "Next morning" passage, which instantly pops in instead of slowly fading in as usual.(enchant: ?Link, (t8n-arrive: "instant"))
causes ALL passage links to use the instant pop-in. This is best used in a "header" or "footer" tagged passage.
Details:
This macro accepts the exact same transition names as (transition:).
- "instant" (causes the passage to instantly vanish)
- "dissolve" or "fade" (causes the passage to gently fade out)
- "flicker" (causes the passage to roughly flicker out - don't use with a long (transition-time:))
- "shudder" (causes the passage to disappear while shaking back and forth)
- "rumble" (causes the passage to instantly appear while shaking up and down)
- "slide-right" (causes the passage to slide in from left to right)
- "slide-left" (causes the passage to slide in from right to left)
- "slide-up" (causes the passage to slide in from bottom to top)
- "slide-down" (causes the passage to slide in from top to bottom)
- "fade-right" (causes the passage to gently fade in while sliding rightward)
- "fade-left" (causes the passage to gently fade in while sliding leftward)
- "fade-up" (causes the passage to gently fade in while sliding upward)
- "fade-down" (causes the passage to gently fade in while sliding downward)
- "pulse" (causes the passage to disappear while pulsating rapidly)
- "zoom" (causes the passage to scale up from the mouse pointer)
- "blur" (causes the passage to appear from a blur)
Attaching this macro to a hook that isn't a passage link won't do anything (no error message will be produced).
You can't combine transitions by adding them together, like you can with (text-style:) -
(t8n-depart:"dissolve")+(t8n-depart:"shudder")
won't make a transition that simultaneously dissolve-fades and shudders.
The "blur" transition will not work in Internet Explorer 10 or 11.
See also:
The (transition-skip: ) macro
(transition-skip: Number) → Changer
Also known as: (t8n-skip:)
A changer that, when added to a (transition:) changer, allows the player to skip or accelerate the transition by holding down a keyboard key or mouse button, or by touching the touch device.
Example usage:
(t8n:"slide-right")+(t8n-time:3s)+(t8n-skip:0.2s)[OK! I'm comin'!]
makes the text slide in from the right,
but only after 3 seconds have passed... but if the player holds a key, mouse button, or the screen, it gets
advanced by an additional 0.2 seconds each millisecond they hold.
Rationale:
It's tempting to use transitions a lot in your story, but these can come at a cost to the player - watching and waiting for transitions to complete can be tiring and detrimental to your story's pacing, especially if they have to revisit certain parts of your story a lot. This macro can help by providing them with a means of skipping or accelerating the transitions if they so choose.
Details:
The number given is an amount of milliseconds (or, if suffixed with s
, seconds) to advance the transition. For each
millisecond of the transition, Harlowe checks if a key or button is held, and if so, then it is advanced
by the given number (in addition to the elapsed millisecond).
If a non-positive number is given, an error will be produced.
This effect advances both a transition's (transition-time:)s and (transition-delay:)s.
See also:
(transition:), (transition-delay:), (transition-time:)
The (animate: ) macro
(animate: HookName, String, [Number]) → Command
A command that causes a hook to briefly animate, as if a (transition:) was applied to it. The length of time that the animation plays can be optionally altered by providing a number.
Example usage:
(after: 15s)[You'd better get going now, pardner. (animate:?passage's links, "rumble")]
causes all of the links in the passage to briefly
shake using the "rumble" transition after 15 seconds have passed.
Rationale:
Transitions allow incoming text to animate in a visually stylish fashion, but there are times you might want already displayed text to suddenly animate, as if it had just transitioned in anew. This command can be useful, when used sparingly, to draw the attention of the player toward a particular part of the passage, such as a link, or an easily missed word, after they click a link or wait a certain amount of time. It can be particularly interesting when used to draw attention to a part that, until then, had nothing visually remarkable about it, so as to highlight it for only a moment.
Details:
(animate:) recognises the same transition names as (transition:), except for "instant" (which obviously cannot be animated). These names are:
- "dissolve" or "fade" (causes the hook to gently fade in)
- "flicker" (causes the hook to roughly flicker in - don't use with a long (transition-time:))
- "shudder" (causes the hook to instantly appear while shaking back and forth)
- "rumble" (causes the hook to instantly appear while shaking up and down)
- "slide-right" (causes the hook to slide in from left to right)
- "slide-left" (causes the hook to slide in from right to left)
- "slide-up" (causes the hook to slide in from bottom to top)
- "slide-down" (causes the hook to slide in from top to bottom)
- "fade-right" (causes the hook to gently fade in while sliding rightward)
- "fade-left" (causes the hook to gently fade in while sliding leftward)
- "fade-up" (causes the hook to gently fade in while sliding upward)
- "fade-down" (causes the hook to gently fade in while sliding downward)
- "pulse" (causes the hook to instantly appear while pulsating rapidly)
- "zoom" (causes the hook to scale up from the mouse pointer)
- "blur" (causes the hook to appear from a blur)
The optional time value, which alters the animation's length of time, corresponds to (transition-time:). Additional alterations to the animation can be given by attaching the other two transition changers, (transition-delay:) and (transition-skip:), to this command.
You may notice that other, permanent animations are available as (text-style:) options. (animate:)'s animations operate separately to those, and animations unique to (text-style:) can't be temporarily applied with this macro. Instead, use (change:) with (text-style:) to apply those animations.
See also:
(show:), (rerun:), (transition:)
The (goto-url: ) macro
(goto-url: String) → Command
When this command is used, the player's browser will immediately attempt to leave the story's page, and navigate to the given URL in the same tab. If this succeeds, then the story session will "end".
Example usage:
(goto-url: "http://www.example.org/")
Details:
If the given URL is invalid, no error will be reported - the browser will simply attempt to open it anyway.
Much like the <a>
HTML element, the URL is treated as a relative URL if it doesn't start
with "http://", "https://", or another such protocol. This means that if your story file is
hosted at "http://www.example.org/story.html", then (open-url: "page2.html")
will actually open
the URL "http://www.example.org/page2.html".
This command can't have changers attached - attempting to do so will produce an error.
See also:
The (open-url: ) macro
(open-url: String) → Command
When this macro is evaluated, the player's browser attempts to open a new tab with the given URL. This will usually require confirmation from the player, as most browsers block Javascript programs such as Harlowe from opening tabs by default.
Example usage:
(open-url: "http://www.example.org/")
Details:
If the given URL is invalid, no error will be reported - the browser will simply attempt to open it anyway.
Much like the <a>
HTML element, the URL is treated as a relative URL if it doesn't start
with "http://", "https://", or another such protocol. This means that if your story file is
hosted at "http://www.example.org/story.html", then (open-url: "page2.html")
will actually open
the URL "http://www.example.org/page2.html".
This command can't have changers attached - attempting to do so will produce an error.
See also:
The (page-url: ) macro
(page-url: ) → String
This macro produces the full URL of the story's HTML page, as it is in the player's browser.
Example usage:
(if: (page-url:) contains "#cellar")
will be true if the URL contains the #cellar
hash.
Details:
This may be changed in a future version of Harlowe to return a datamap containing more descriptive values about the URL, instead of a single string.
The (scroll: ) macro
(scroll: HookName, Number or HookName) → Command
This command, when given a HookName, followed by a fraction (a number between 0 and 1), will change the scroll position of every hook with that name to the percentage of their height signified by the fraction. You may alternatively give another HookName instead, which, if a hook of that name is inside the first hook, will scroll the first hook and each containing hook such that the second hook is visible.
Example usage:
(scroll:?page, 1)
will scroll the entire page to the bottom.(scroll:?page, 0.5)
will scroll the entire page to the middle.(scroll:?page, ?danger)
will scroll the entire page such that the first hook named ?danger is visible.(scroll:?A, ?C)
will change the scroll position of hooks named ?A such that hooks named ?C are visible. Note that if the hook named ?A is itself not visible (because it's offscreen) then the page's scroll position will not change to make it visible.
Rationale:
When you're using a large number of prose-altering macros in your story, such as (replace:) or (show:), you'll often want to make sure the player can see the changed text immediately. This may be complicated by the fact that they could be playing the story on a small screen. The (scroll:) command lets you force the page to be in a particular scroll position whenever you want, ensuring that the affected prose is visible.
Alternatively, when using scrollable boxes like (box:), you may want to change their initial scroll position from the default (the top), for any number of reasons.
Details:
This command only changes the Y (vertical) scroll position of the given hooks.
This command does nothing if no hooks of the given name exist in the passage, or if none of them have vertical scroll bars. Also, it does nothing if the second HookName doesn't correspond to any hooks inside the first hook.
Obviously, giving a non-fractional number will cause an error.
Due to browser limitations, giving a fractional number will not work in Internet Explorer 10. If you wish for your story to support that browser, please give a second HookName instead of a percentage.
The (mouseover: ) macro
(mouseover: HookName or String, [Changer or Lambda]) → Changer
A variation of (click:) that acts as if (action:'mouseover')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseout: ) macro
(mouseout: HookName or String, [Changer or Lambda]) → Changer
A variation of (click:) that acts as if (action:'mouseout')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseover-replace: ) macro
(mouseover-replace: HookName or String, [Changer or Lambda]) → Changer
A variation of (click-replace:) that acts as if (action:'mouseover')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseover-append: ) macro
(mouseover-append: HookName or String, [Changer or Lambda]) → Changer
A variation of (click-append:) that acts as if (action:'mouseover')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
The (mouseover-prepend: ) macro
(mouseover-prepend: HookName or String, [Changer or Lambda]) → Changer
A variation of (click-prepend:) that acts as if (action:'mouseover')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseout-replace: ) macro
(mouseout-replace: HookName or String, [Changer or Lambda]) → Changer
A variation of (click-replace:) that acts as if (action:'mouseout')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseout-append: ) macro
(mouseout-append: HookName or String, [Changer or Lambda]) → Changer
A variation of (click-append:) that acts as if (action:'mouseout')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseout-prepend: ) macro
(mouseout-prepend: HookName or String, [Changer or Lambda]) → Changer
A variation of (click-prepend:) that acts as if (action:'mouseout')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseover-goto: ) macro
(mouseover-goto: HookName or String, String) → Command
A variation of (click-goto:) that acts as if (action:'mouseover')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseout-goto: ) macro
(mouseout-goto: HookName or String, String) → Command
A variation of (click-goto:) that acts as if (action:'mouseout')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseover-undo: ) macro
(mouseover-undo: HookName or String, String) → Command
A variation of (click-undo:) that acts as if (action:'mouseover')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
The (mouseout-undo: ) macro
(mouseout-undo: HookName or String, String) → Command
A variation of (click-undo:) that acts as if (action:'mouseout')
was provided to it as the optional second changer
(in addition to any other changers).
Details:
This macro is currently deprecated - while you may use it in this version, it is likely to be removed in a potential Harlowe 4.0. Use of the (action:) macro is recommended instead.
See also:
Special keywords
exits keyword
exits → Number
Also known as: exit
This keyword (which can alternatively be written as "exit") evaluates to the number of currently available "exits" in a passage - the number of link, mouseover, and mouseout elements that are active on the page, which could lead to new content and progress.
This keyword is designed to be used with (live:) and (event:) - you can make a hook only
be revealed when a certain number of exits are available, with (event: when exits < 3)
and similar. The (more:) macro is a shorthand
form for (event: when exits is 0)
.
The complete list of elements considered to be "exit" elements is as follows:
- Links created by (link:), (link-repeat:), (link-reveal:), (link-goto:), (link-reveal-goto:), and (link-show:).
- Passage links (which are the same as (link-goto:) links).
- Links created by (click:), (click-replace:), (click-append:), (click-prepend:), and (click-goto:).
- Mouseover areas created by (mouseover:), (mouseover-replace:), (mouseover-append:), (mouseover-prepend:), and (mouseover-goto:).
- Mouseout areas created by (mouseout:), (mouseout-replace:), (mouseout-append:), (mouseout-prepend:), and (mouseout-goto:).
Do note the following, however.
- Multiple passage links that lead to the same passage (such as
[[A->Dead]] [[B->Dead]] [[C->Dead]]
) are all counted separately. - As of Harlowe 3.1.0, this does not consider (link-undo:) macros to be exits, as they tend to only undo game progress.
- This will also not consider (event:) or (live:) macros to be exits, even if they are guaranteed to display their hooks eventually.
- As with macros like (replace:), the
exits
keyword can't see forward to forthcoming elements, unless they've already appeared. For instance, the (print:) in(print:exits is 1) [[Retreat->Hall]]
will showfalse
, because the link after it hasn't appeared in the passage yet, but the (print:) in(live:20s)[(print:exits is 1)] [[Retreat->Hall]]
will showtrue
. - This can't be used in a (storylet:)'s lambda, because those lambdas are only checked when you're outside the passage.
Finally, the "undo" and "redo" links in the sidebar will not be counted, either.
it keyword
it → Any
Like all identifiers, it
is case-insensitive: IT
, iT
and It
are all acceptable as well.
This keyword is usually a shorthand for the most recently evaluated expression's leftmost value. It lets you write
(if: $candles < 2 and it > 5)
instead of (if: $candles < 2 and $candles > 5)
, or (set: $candles to it + 3)
instead of (set: $candles to $candles + 3)
. (You can't, however, use it in a (put:) or (move:) macro:
(put:$red + $blue into it)
is invalid.)
Since it
uses the most recent expression's leftward value, (print: $red > 2 and it < 4 and $blue > 2 and it < 4)
is the same as
(print: $red > 2 and $red < 4 and $blue > 2 and $blue < 4)
. This value of it
changes to $blue in it < 4
because
the expression $blue > 2
was evaluated by Harlowe just before, and $blue is the leftmost value inside that expression.
(To get a greater sense of how Harlowe evaluates expressions, use Debug View in Harlowe's Debug Mode, then click on a 🔍
button in the passage.)
Inferred it
:
In some situations, the it
keyword will be inserted automatically by Harlowe when the story runs. If you write an
incomplete comparison expression where the left-hand side is missing, like (print: $red > 2 and < 4)
,
then, when running, the it
keyword will automatically be inserted into the absent spot - producing, in this case,
(print: $red > 2 and it < 4)
.
Inferred comparisons:
In addition to the above, there are some situations involving chains of and
and or
operators where Harlowe can insert
a missing it
keyword and the most recently-evaluated comparison operator. If you write (if: $a > 2 and 3)
, then Harlowe
will decide, since it is incorrect to use and
directly with numbers, that what you actually meant was (if: $a > 2 and it > 3)
.
Special case - right-side inferred comparisons:
Harlowe will make inferences even if the comparison is on the right side of the chain of and
and or
operators, such as (if: 3 and 4 < $a)
.
In these cases, however, Harlowe will slightly change the rules of the it
keyword to conform to English grammar intuitions. (if: 3 and 4 < $a)
intuitively means "if both 3 and 4 are less than $a" in English. So, it is interpreted by Harlowe as follows.
Since 4 < $a
is the only complete expression, it is evaluated first. Then, the rightmost value, $a, becomes the it
identifier's value.
Then, 3
becomes 3 < it
. The macro call is now equivalent to (if: 3 < $a and 4 < $a)
.
Note that, since this special case only applies to it
keywords that Harlowe inserts by itself, you shouldn't really worry about it when
writing your stories. Just be aware of how right-side inferred comparisons work.
Special case - it
in lambdas:
There is another special case regarding the it
identifier: inside where
, via
or making
lambdas, it
refers to the data value
that the lambda is currently operating on, saving you from having to write it, or, in some cases, name it at all. A lambda like
_num where _num > 4
can be shortened to just where it > 4
- the it
identifier in this case replacing the _num
temp variable entirely.
The its
variant:
If the it
keyword equals a datamap, string, array, or other "collection" data type, then you can access data values
using the its
variant - (print: $red is 'egg' and its length is 3)
or (set:$red to its 1st)
. Much like the 's
operator, you can use computed values with its
- (if: $red's length is 3 and its $position is $value)
will work as
expected.
pos keyword
pos → Number
Used exclusively in lambdas, this keyword evaluates to the position of the current data value that this lambda is processing.
Consider a macro that uses lambdas, such as (altered:) - you give it a lambda, and then one or more data values, such as
in (altered: via it + (str:pos), "A","B","C")
. When the lambda is processing "A", the pos identifier is 1, for "B" it's 2, and for
"C" it's 3. This can be used for a number of purposes. You can attach an ascending number to each data value, as in that example.
You can make only odd-numbered values be altered by using (cond: pos is an odd, it, it + (str:pos))
(which uses the "odd" datatype).
You can make every third value be followed by a comma, by using (cond: pos % 3 is 2, it, it + ',')
.
Note that this only corresponds to the position of the values when given to the macro - if you use the ...
operator to spread arrays'
values into a macro, such as (find: where it is > pos, ...$array1, ...$array2, ...$array3)
, then values from $array2 and $array3 will not
have a pos that corresponds to their placement inside those arrays, but rather relative to all of the values, including those in $array1.
Make sure you do NOT write this as its pos
- the pos is not a data value of the data itself! If it
was (dm:'HP',20,'XP',12)
, its pos
would cause an error, as there is no "pos" value in that datamap.
Using this anywhere other than a lambda, or using it in a 'when' lambda (which doesn't operate over a sequence of values), will cause an error.
time keyword
time → Number
This keyword evaluates to the number of milliseconds passed since the passage
was displayed. Its main purpose is to be used alongside changers
such as (live:), (event:) or (link:). (link:"Click")[(if: time > 5s)[...]]
, for instance,
can be used to determine if 5 seconds have passed since this passage was displayed,
and thus whether the player waited 5 seconds before clicking the link.
It's recommended that you compare values of time using the ms
or s
suffixes for number data.
See the article on number data for more information.
When the passage is initially being rendered, time
will be 0.
time
used in (display:) macros will still produce the time of the host passage, not the
contained passage. So, you can't use it to determine how long the (display:)ed passage
has been present in the host passage.
As of Harlowe 3.3.4, when testing your story in Debug Mode, you can add a multiplier to time
(so as to quickly advance
the passage to an important part, or linger on an important part) by using the "Speed" dropdown in the Tools panel. If you change it to 0.5x,
time
's number will be multiplied by 0.5, and so forth. Note that this option will currently not affect the speed of
transitions (using (t8n-delay:) or (t8n-time:)).
turns keyword
turns → Number
Also known as: turn
This keyword (which can alternatively be written as "turn") evaluates to the number of turns that have occurred in this game. A "turn" is any movement to a passage, including movements back to the same passage, by passage links or by various (go-to:)-related macros. (redirect:) does not cause a new turn to occur, so using it will not increase this value.
Much like the "visits" keyword, its main purpose is to be used with simple macros like (nth:) or (if:) to change what
text is displayed, such as by (if: turns > 7)
. It is also useful in (storylet:) lambdas, such as ( when turns > 7)
,
for storylets that should only be available when a certain number of turns have elapsed.
For testing purposes, it can be convenient to temporarily alter turns
's value, so as to recreate a
certain game state. The (mock-turns:) macro, usable only in debug mode, lets you artificially increase the number
that this evaluates to.
visits keyword
visits → Number
Also known as: visit
This keyword (which can alternatively be written as "visit") always equals the number of times the current passage has been visited this game, including the current visit.
Much like the "turns" keyword, its main purpose is to be used in (if:) macros, such as (if: visits is 1)
,
or (if: visits > 4)
. If you use one particular formulation a lot in your story, such as (if: visits is 1)
, you can (set:) the (if:)
into a variable using (set: $first to (if:visits is 1))
and then use $first in its place, such as in
$first[You've discovered a new island.]
.
Similarly, it is also useful with the (cond:) and (nth:) macros - the latter letting you simply use visit
as its first value to vary the results based on the number of times the passage is visited.
This can also be used to great effect in (storylet:) macros, such as ( when visits is 0)
, where it will always refer to the containing
passage itself. When Harlowe decides whether this passage is available to (open-storylets:), this will often be 0, but when
actually visiting the passage, it will be at least 1.
visits
used in (display:) macros will still produce the number of times the host passage was visited,
not the contained passage. So, you can't use it to determine how many times the (display:)ed passage
has been (display:)ed.
Using (redirect:) to go to a passage will count as a "visit" for that passage, even though it doesn't start a new turn.
For testing purposes, it can be convenient to temporarily alter visits
's value, so as to recreate a
certain game state. The (mock-visits:) macro, usable only in debug mode, lets you increase the number of
times certain passages have been "visited", so that this keyword produces higher numbers when in those passages.
By default, Harlowe records an unlimited amount of passage visits. However, you can use the (forget-visits:) macro to make Harlowe "forget" visits that are a certain number of turns old.
Special passage tags
header tag
It is often very useful to want to reuse a certain set of macro calls in every passage,
or to reuse an opening block of text. You can do this by giving the passage the special
tag header
, or footer
. All passages with these tags will have their source text included at the top
(or, for footer
, the bottom) of every passage in the story, as if by an invisible (display:) macro call.
If many passages have the header
tag, they will all be displayed, ordered by their passage
name, sorted alphabetically, and by case (capitalised names appearing before lowercase names).
footer tag
This special tag is identical to the header
tag, except that it places the passage
at the bottom of all visited passages, instead of the top.
startup tag
This special tag is similar to header
, but it will only cause the passage
to be included in the very first passage in the game.
This is intended to simplify the story testing process: if you have setup code which creates variables used throughout the entire story, you should put it in a passage with this tag, instead of the starting passage. This allows you to test your story from any passage, and, furthermore, easily change the starting passage if you wish.
All passages tagged with startup
will run, in alphabetical order
by their passage name, before the passages tagged header
will run.
debug-header tag
This special tag is similar to the header
tag, but only causes the passage
to be included if you're running the story in debug mode.
This has a variety of uses: you can put special debug display code in this passage, which can show the status of certain variables or provide links to change the game state as you see fit, and have that code be present in every passage in the story, but only during testing.
All passages tagged with debug-header
will run after the passages tagged header
will run,
ordered by their passage name, sorted alphabetically, and by case (capitalised names appearing
before lowercase names).
debug-footer tag
This special tag is identical to the debug-header
tag, except that it places the passage
at the bottom of all visited passages, instead of the top.
All passages tagged with debug-footer
will run, in alphabetical order
by their passage name, after the passages tagged footer
have been run.
debug-startup tag
This special tag is similar to the startup
tag, but only causes the passage
to be included if you're running the story in debug mode.
This has a variety of uses: you can put special debugging code into this passage, or set up a late game state to test, and have that code run whenever you use debug mode, no matter which passage you choose to test.
All passages tagged with debug-startup
will run, in alphabetical order
by their passage name, after the passages tagged startup
will run.
Types of data
Any data
A macro that is said to accept "Any" will accept any kind of data without complaint, as long as the data does not contain any errors.
Array data
There are occasions when you may need to work with a whole sequence of values at once. For example, a sequence of adjectives (describing the player) that should be printed depending on what a numeric variable (such as a health point variable) currently is. You could create many, many variables to hold each value, but it is preferable to use an array containing these values.
Arrays are one of the two major "data structures" you can use in Harlowe. The other, datamaps, are created with (dm:). Generally, you want to use arrays when you're dealing with values whose order and position relative to each other matter. If you instead need to refer to values by a name, and don't care about their order, a datamap is best used.
You can refer to and extract data at certain positions inside arrays using 1st
, 2nd
, 3rd
, and so forth:
$array's 1st
, also written as 1st of $array
, refers to the value in the first position. Additionally, you can
use last
to refer to the last position, 2ndlast
to refer to the second-last, and so forth. Arrays also have
a length
number: $array's length
tells you how many values are in it. If you can't determine the exact position
to remove an item from (because it's dependent on a variable), you can use an expression, in brackers,
after it: $array's ($pos - 3)
. This syntax can be chained: if an array is inside another data structure (for instance, by
(set: $array to (a:(a:1,2),(a:2,3)))
) then you can write $array's 1st's 1st
to access the 1 stored in the inner array.
Note: While you can normally display the contents of variables by simply placing their names directly in passage prose,
such as $votes
, you have to use another macro, such as (print:), to display the contents of arrays, such as (print: $votes's 1st)
.
To see if arrays contain certain values, you can use the contains
and is in
operators like so: $array contains 1
is true if it contains the number 1 anywhere, and false if it does not. 1 is in $array
is another way to write that.
The is not in
operator is the opposite of is in
, and is used to check that values aren't in arrays.
If you want to check if an array contains some, or all of the values, in another array (without needing to be in the
same order), you can compare with a special some
(also known as any
- no relation to the any
datatype) or all
name on the other array: $array contains any of (a:2,4,6)
,
and $array contains all of (a:2,4,6)
will check if $array
contains some, or all, of the numbers 2, 4 and 6.
If you want to check if an array starts or ends with with a certain sequence of values, start
and end
data names
can be used with is
and is not
- $array's start is (a:2,4)
is the same as $array's 1stto2nd is (a:2,4)
, and
$array's end is (a:3,6,9)
is the same as $array's 3rdlasttolast is (a:3,6,9)
.
(Incidentally, some
and all
can also be used with other operators, like is
, is not
, >
, <
, >=
, and <=
,
to compare every value in the array with a number or other value. For instance, all of (a:2,4) >= 2
is true, as is
some of (a:2,4) >= 4
.)
For a more thorough check of the contents of an array, you can use matches
and a datatype pattern. For instance,
$array matches (a: num, num)
lets you check that $array contains exactly two numbers, and $array's start matches (a: 2, num)
lets you check that $array starts with 2 followed by another number. See the datatype article for more details.
Arrays may be joined by adding them together: (a: 1, 2) + (a: 3, 4)
is the same as (a: 1, 2, 3, 4)
.
You can only join arrays to other arrays. To add a bare value to the front or back of an array, you must
put it into an otherwise empty array using the (a:) macro: $myArray + (a:5)
will make an array that's just
$myArray with 5 added on the end, and (a:0) + $myArray
is $myArray with 0 at the start.
You can make a subarray (another array containing only certain positioned values from the first array) by
providing an array of numbers as a data name, to indicate which positions to include - $arr's (a:1,3)
produces an array with only the first and third values of $arr. Negative values indicate positions counted from the array's
end - $arr's (a:-4,-2)
is the same as (a: $arr's 4thlast, $arr's 2ndlast)
. If you want to make a subarray consisting
of a large number of consecutive fixed positions, special data names can be used - $array's 1stto3rd
indicates the
"1st to 3rd" positions, and is the same as $array's (a:1,2,3)
. $array's 3rdlasttolast
is the same as $array's (a:-3,-2,-1)
.
You can subtract items from arrays (that is, create a copy of an array with certain values removed) using
the -
operator: (a:"B","C") - (a:"B")
produces (a:"C")
. Note that multiple copies of a value in an array will all
be removed by doing this: (a:"B","B","B","C") - (a:"B")
also produces (a:"C")
.
You may note that certain macros, like (either:), accept sequences of values. A special operator, ...
, exists which
can "spread out" the values inside an array, as if they were individually placed inside the macro call.
(either: ...$array)
is a shorthand for (either: $array's 1st, $array's 2nd, $array's 3rd)
, and so forth for as many
values as there are inside the $array. Note that you can still include values after the spread: (either: 1, ...$array, 5)
is valid and works as expected.
To summarise, the following operators work on arrays.
Operator | Purpose | Example |
---|---|---|
is |
Evaluates to boolean true if both sides contain equal items in an equal order, otherwise false . |
(a:1,2) is (a:1,2) (is true) |
is not |
Evaluates to true if both sides differ in items or ordering. |
(a:4,5) is not (a:5,4) (is true) |
contains |
Evaluates to true if the left side contains the right side. |
(a:"Ape") contains "Ape" (a:(a:99)) contains (a:99) (a:1,2) contains some of (a:2,3) (a:1,2) contains all of (a:2,1) |
does not contain |
Evaluates to true if the left side does not contain the right side. |
(a:"Ape") does not contain "Egg" |
is in |
Evaluates to true if the right side contains the left side. |
"Ape" is in (a:"Ape") (a:99) is in (a:(a:99)) some of (a:2,3) is in (a:1,2) all of (a:2,1) is in (a:1,2) |
is not in |
Evaluates to true if the right side does not contain the left side. |
"Blood" is not in (a:"Sweat","Tears") (a:98) is not in (a:(a:99)) some of (a:3,2) is not in (a:1,2) |
+ |
Joins arrays. | (a:1,2) + (a:1,2) (is (a:1,2,1,2) ) |
- |
Subtracts arrays, producing an array containing every value in the left side but not the right. | (a:1,1,2,3,4,5) - (a:1,2) (is (a:3,4,5) ) |
... |
When used in a macro call, it separates each value in the right side. | (a: 0, ...(a:1,2,3,4), 5) (is (a:0,1,2,3,4,5) ) |
's |
Obtains the item at the right numeric position, or the length , some or all values. 's cannot have any spaces to its left. |
(a:"Y","Z")'s 1st (is "Y")(a:4,5)'s (2) (is 5)(a:5,5,5)'s length (is 3) |
of |
Obtains the item at the left numeric position, or the length , some or all values. |
1st of (a:"Y","O") (is "Y")(2) of (a:"P","S") (is "S")length of (a:5,5,5) (is 3) |
matches |
Evaluates to boolean true if the array on one side matches the pattern on the other. |
(a:2,3) matches (a: num, num) , (a: array) matches (a:(a: )) |
does not match |
Evaluates to boolean true if the array on one side does not match the pattern on the other. |
(a:2,3) does not match (a: num) , (a: str) does not match (a:(a:'Egg')) |
is a , is an |
Evaluates to boolean true if the right side is a datatype describing the left side. |
(a:2,3) is an array , (a: ) is an empty |
is not a , is not an |
Evaluates to boolean true if the right side is a datatype that does not describe the left side. |
(a:2,3) is not an empty |
And, here are the data names that can be used with arrays.
Data name | Example | Meaning |
---|---|---|
1st ,2nd ,last , etc. |
(a:1,2)'s last , 1st of (a:1,2) |
A single value at the given position in the array. This causes an error if it's past the bounds of the array, |
1stto3rd , 4thlastto2ndlast etc. |
(a:1,2,3,4,5)'s 2ndto5th |
A subarray containing only the values between the given positions (such as the first, second and third for 1stto3rd ). This does NOT cause an error if it passes the bounds of the array - so (a:1,2,3)'s 2ndto5th is (a:2,3) . |
length |
(a:'G','H')'s length |
The length (number of data values) in the array. |
random |
(a:"a","e","i","o","u")'s random (is "a" , "e" , "i" , "o" or "u" ). |
A random value in the array. |
some , any , all |
some of (a:1,2) < 3 , all of (a:1,2) is not 3 |
Usable only with comparison operators, these allow all of the values to be quickly compared. any is an old alias for some that functions identically, but which may be removed in a future version of Harlowe. |
start , end |
start of (a:1,2,3,4) is (a:1,2) , (a:1,2,3,4)'s end is not (a:2,4) |
Usable only with the is , is not , matches and does not match operators, these allow you to compare the start or end of arrays without having to specify an exact range of values to compare. |
Arrays of numbers, such as (a:3,5) |
$array's (a:1,-1) |
A subarray containing just the data values at the given positions in the array. |
A note about random
: this is one of the features that uses Harlowe's pseudo-random number generator. If you use (seed:) at the start of the story,
the selected values will be predetermined based on the seed string, and how many other random macros and features have been used before it.
Bind data
A few macros that produce interactive elements, like (cycling-link:), have the ability to automatically update a variable whenever the
player interacts with them. There needs to be a way to specify which variable these will update: simply giving the macro the variable
itself, such as in (cycling-link: $hat, "Poker visor", "Beret")
, won't work - the value that's currently inside $hat
will be given
instead, as one would expect for every other kind of macro. So, the bind
keyword is needed to make your intent unambiguous:
bind $hat
produces a "bound variable".
One can bind any kind of variable: story-wide variables like $morality
, temp variables like _glance
, and data values and positions
inside them, like $diary's 1st's event
. Once bound, the macro's element will set data to it automatically, as if by a series of
unseen (set:)s or (move:)s.
Two-way binds, created by the 2bind
syntax, enforce an equality that normal binds do not: whenever the variable changes outside
of the element, such as by an (event:)
macro, then the interaction element updates to match, if it can. Thus, there are two bindings
between the data and the element using it: the variable updates when the element changes, and the element updates when the variable changes.
Note that bound variables can't be (set:) into variables themselves, because there's no real point to doing so (and it could lead to a lot of undue confusion).
Operator | Purpose | Example |
---|---|---|
bind |
Binds the named variable on the right. | bind $weapon , bind _hat , bind $profile's age |
2bind |
Double-binds the named variable on the right. | 2bind $weapon , 2bind _hat , 2bind $profile's age |
If you bind an array's random
data name (a data name which normally provides a random value) then Harlowe will pick a random position,
and retain that position for the duration of the bound variable's usage. So, if it picks the 3rd position, the macro using the bound
variable will be consistently bound to the 3rd position's value.
Boolean data
Branching stories involve the player making choices, and the game using its own programmed logic to react to those choices.
Much as how arithmetic involves manipulating numbers with addition, multiplication and such, logic involves manipulating the
values true
and false
using its own operators. Those are not text strings - they are values as fundamental as
the natural numbers. In computer science, they are both called Booleans, after the 19th century mathematician
George Boole.
is
is a logical operator. Just as + adds the two numbers on each side of it, is
compares two values on each
side and evaluates to true
or false
depending on whether they're identical. It works equally well with strings,
numbers, arrays, and anything else, but beware - the string "2"
is not equal to the number 2.
There are several other logical operators available.
Operator | Purpose | Example |
---|---|---|
is |
Evaluates to true if both sides are equal, otherwise false . |
$bullets is 5 |
is not |
Evaluates to true if both sides are not equal. |
$friends is not $enemies |
contains |
Evaluates to true if the left side contains the right side. |
"Fear" contains "ear" |
does not contain |
Evaluates to true if the left side does not contain the right side. |
"Fear" does not contain "eet" |
is in |
Evaluates to true if the right side contains the left side. |
"ugh" is in "Through" |
> |
Evaluates to true if the left side is greater than the right side. |
$money > 3.75 |
>= |
Evaluates to true if the left side is greater than or equal to the right side. |
$apples >= $carrots + 5 |
< |
Evaluates to true if the left side is less than the right side. |
$shoes < $people * 2 |
<= |
Evaluates to true if the left side is less than or equal to the right side. |
65 <= $age |
and |
Evaluates to true if both sides evaluates to true . |
$hasFriends and $hasFamily |
or |
Evaluates to true if either side is true . |
$fruit or $vegetable |
not |
Flips a true value to a false value, and vice versa. |
not $stabbed |
matches |
Evaluates to true if one side is a pattern or datatype describing the other. |
boolean matches true |
does not match |
Evaluates to true if one side does not describe the other. |
boolean does not match "true" |
is a , is an |
Evaluates to boolean true if the right side is boolean and the left side is a boolean. |
$wiretapped is a boolean |
is not a , is not an |
Evaluates to boolean true if the right side does not describe the left side. |
"Boo" is not an empty , 2 is not an odd |
Conditions can quickly become complicated. The best way to keep things straight is to use parentheses to group sub-statements and express order of operations.
Changer data
Changers are similar to commands, but they only have an effect when they're attached to hooks, passage links and commands, and modify them in some manner. Macros that work like this include (text-style:), (font:), (t8n:), (text-rotate:), (hook:), (click:), (link:), (for:), (if:), and more.
(set: $sawDuckHarbinger to true)
(if: $sawDuckHarbinger)[You still remember spying the black duck, harbinger of doom.]
(t8n-depart: "dissolve")[[Return to the present]]
You can save changers into variables, and re-use them many times in your story:
(set: $robotic to (font:'Courier New'))
$robotic[Hi, it's me. Your clanky, cold friend.]
Alternatively, you may prefer to use the (change:) or (enchant:) macro to accomplish the same thing using only hook names:
|robotic>[Hi, it's me. Your clanky, cold friend.]
(change: ?robotic, (font:'Courier New'))
Changers can be combined using the +
operator: (text-colour: red) + (font: "Courier New")[This text is red Courier New.]
styles the text using both changers at once. These combined changers, too, can be saved in variables or used with (change:)
or (enchant:).
(set: _alertText to (font:"Courier New") + (text-style: "buoy")+(text-colour:"#e74"))
_alertText[Social alert: no one read the emails you sent yesterday.]
_alertText[Arithmetic error: I forgot my seven-times-tables.]
CodeHook data
Several macros, such as (dialog:) or (append-with:), are used to render some markup code stored in a string. However, markup code stored in strings can be difficult to write or read in the editor, because there's no syntax highlighting for the interior of string. It is also sometimes desirable to have markup code in strings appear different to other strings in Debug Mode, so that you can more easily tell what the string is used for in the game.
Code hooks can be used as alternatives to strings in these cases. You may think of them as "hooks" that
are written inside macro calls, as data provided to them. Place markup code between [
and ]
symbols, in the
same way that strings are between pairs of "
or '
symbols.
Note that unlike strings, you can nest hooks inside code hooks without needing to escape the ]
symbol.
(print:[Good afternoon.(if:visits > 1)[ I'm glad to see you again]])
is a valid code hook.
The (macro:) macro only accepts code hooks for the inner code of the custom macro. The contents of this hook will not be displayed when the custom macro runs, so you can put any number of comments and remarks inside it, for your own benefit.
Colour data
Colours are special data values which can be provided to certain styling macros, such as (bg:) or (text-colour:). You can use built-in named colour values, or create other colours using the (rgb:) or (hsl:) macros.
The built-in values consist of the following:
Value | HTML colour equivalent |
---|---|
red |
#e61919 |
orange |
#e68019 |
yellow |
#e5e619 |
lime |
#80e619 |
green |
#19e619 |
aqua or cyan |
#19e5e6 |
blue |
#197fe6 |
navy |
#1919e6 |
purple |
#7f19e6 |
magenta or fuchsia |
#e619e5 |
white |
#fff |
black |
#000 |
grey or gray |
#888 |
transparent |
transparent |
(These colours were chosen to be visually pleasing when used as both background colours and text colours, without the glaring intensity that certain HTML colours, like pure #f00 red, are known to exhibit.)
In addition to these values, and the (rgb:) macro, you can also use HTML hex notation to specify
colours, such as #691212
or #a4e
. (Note that these are not strings, but bare values - (bg: #a4e)
is valid, as is (bg:navy)
.) Of course, HTML hex notation is notoriously hard to read and write, so this
isn't recommended.
If you want to quickly obtain a colour which is the mixture of two others, you can mix them
using the +
operator: red + orange + white
produces a mix of red and orange, tinted
white. #a4e + black
is a dim purple. Note that the transparent
built-in value allows you to make colours
partly transparent by mixing them with it. If you want to mix colours in different proportions, such
as by making a 90% white, 10% yellow shade, then the (mix:) macro is also available to use.
Like datamaps, colour values have a few read-only data names, which let you examine the red, green and blue components that make up the colour, as well as its hue, saturation and lightness, its alpha transparency, and a datamap showing its lch form (in the same values given to the (lch:) macro).
Data name | Example | Meaning |
---|---|---|
r |
$colour's r |
The red component, a whole number from 0 to 255. |
g |
$colour's g |
The green component, a whole number from 0 to 255. |
b |
$colour's b |
The blue component, a whole number from 0 to 255. |
h |
$colour's h |
The hue angle in degrees, a whole number from 0 to 359. |
s |
$colour's s |
The saturation percentage, a fractional number from 0 to 1. |
l |
$colour's l |
The lightness percentage, a fractional number from 0 to 1. |
a |
$colour's a |
The alpha percentage, a fractional number from 0 to 1. |
lch |
$colour's lch |
A datamap of LCH values for this colour. |
These values can be used in the (hsl:), (rgb:) and (lch:) macros to produce further colours. Note that some of these values
do not transfer one-to-one between representations! For instance, the hue of a gray is essentially irrelevant, so grays
will usually have a h
value equal to 0, even if you provided a different hue to (hsl:). Furthermore, colours with a
lightness of 1 are always white, so their saturation and hue are irrelevant.
The lch
value produces a datamap containing these values.
Data name | Example | Meaning |
---|---|---|
l |
$colour's lch's l |
The lightness percentage, a fractional number from 0 to 1. (Not the same as $colour's l !) |
c |
$colour's lch's c |
The chroma component, a whole number from 0 to 230 (but which is usually below 132). |
h |
$colour's lch's h |
The hue angle in degrees, a whole number from 0 to 359. |
Colours, when used in passage prose or given to (print:), produce a square swatch containing the colour. This is a <tw-colour>
element, but otherwise has no other features or capabilities and is intended solely for debugging purposes.
Command data
Commands are special kinds of data which perform an effect when they're placed in the passage. Most commands are created from macros placed directly in the passage, but, like all forms of data, they can be saved into variables using (set:) and (put:), and stored for later use.
Macros that produce commands include (alert:), (save-game:), (load-game:), and more.
Commands like (display:), (print:), (link:), (show:) and so on are used to print data or an interactive element into the passage. These elements can be styled like hooks, by attaching changers to the macro, as if it was a hook.
In addition to their visual appearance, you can also change what passage transitions links use, by applying (t8n-depart:) and (t8n-arrive:). (Note that since normal passage links are identical to the (link-goto:) macro, you can also attach changers to passage links.)
CustomMacro data
These are custom macros produced by the (macro:) and (partial:) macros. You can (and should) store them in variables using (set:),
and call them like any other macro, by using the variable instead of a name: ($someCustomMacro:)
is how you would
call a custom macro stored in the variable $someCustomMacro, and (_anotherCustomMacro:)
is how you would
call a custom macro stored in the temp variable _anotherCustomMacro.
Custom macros created with (macro:) have a single data name that you can examine.
Data name | Example | Meaning |
---|---|---|
params |
$customMacro's params , params of $customMacro |
An array containing all of the TypedVar data given to the (macro:) macro when this was created. |
Placing custom macros directly into passage prose, such as by calling (macro:) outside of a (set:) or another data-storing macro, or writing a custom macro call incorrectly, will cause an error.
For more information about custom macros, see the (macro:) macro's article.
Datamap data
There are occasions when you may need to work with collections of values that "belong" to a specific object or entity in your story - for example, a table of numeric "statistics" for a monster - or that associate a certain kind of value with another kind, such as a combination of adjectives ("slash", "thump") that change depending on the player's weapon name ("claw", "mallet") etc. You can create datamaps to keep these values together, move them around en masse, and organise them.
Datamaps are one of the two major "data structures" you can use in Harlowe. The other, arrays, are created with (a:). You'll want to use datamaps if you want to store values that directly correspond to strings, and whose order and position do not matter. If you need to preserve the order of the values, then an array may be better suited.
Datamaps consist of several string names, each of which maps to a specific value. $animals's frog
and frog of $animals
refers to the value associated with the name 'frog'. You can add new names or change existing values by using (set:) -
(set: $animals's wolf to "howl")
. You can express the name as a bare word if it doesn't have a space or other punctuation in it - $animals's frog
is OK, but
$animals's komodo dragon
is not. In that case, you'll need to always supply it as a string - $animals's "komodo dragon"
.
This syntax can be chained: if a datamap is inside another data structure (for instance, by
(set: $arr to (a:(dm:'name', 'silver ring', 'resaleValue', 250),(dm:'name', 'a button', 'resaleValue', 0)))
)
then you can write $arr's 1st's resaleValue
to access the 250 in the first datamap.
Note: While you can normally display the contents of variables by simply placing their names directly in passage prose,
such as $sandwich
, you have to use another macro, such as (print:), to display the contents of datamaps, such as (print: $sandwich's bread)
.
Datamaps may be joined by adding them together: (dm: "goose", "honk") + (dm: "robot", "whirr")
is the same as
(dm: "goose", "honk", "robot", "whirr")
. In the event that the second datamap has the same name as the first one,
it will override the first one's value - (dm: "dog", "woof") + (dm: "dog", "bark")
will act as
(dm: "dog", "bark")
.
You may notice that you usually need to know the names a datamap contains in order to access its values. There are certain macros which provide other ways of examining a datamap's contents: (dm-names:) provides a sorted array of its names, (dm-values:) provides a sorted array of its values, and (dm-entries:) provides an array of names and values.
To summarise, the following operators work on datamaps.
Operator | Purpose | Example |
---|---|---|
is |
Evaluates to boolean true if both sides contain equal names and values, otherwise false . |
(dm:"HP",5) is (dm:"HP",5) (is true) |
is not |
Evaluates to true if both sides differ in items or ordering. |
(dm:"HP",5) is not (dm:"HP",4) (is true)(dm:"HP",5) is not (dm:"MP",5) (is true) |
contains |
Evaluates to true if the left side contains the name on the right.(To check that a datamap contains a value, try using contains with (dm-values:)) |
(dm:"HP",5) contains "HP" (is true)(dm:"HP",5) contains 5 (is false) |
does not contain |
Evaluates to true if the left side does not contain the name on the right. |
(dm:"HP",5) does not contain "MP" (is true) |
is in |
Evaluates to true if the right side contains the name on the left. |
"HP" is in (dm:"HP",5) (is true) |
is not in |
Evaluates to true if the right side does not contain the name on the left. |
"XP" is not in (dm:"HP",5) (is true) |
+ |
Joins datamaps, using the right side's value whenever both sides contain the same name. | (dm:"HP",5) + (dm:"MP",5) |
's |
Obtains the value using the name on the right. 's cannot have any spaces to its left. |
(dm:"love",155)'s love (is 155). |
of |
Obtains the value using the name on the left. | love of (dm:"love",155) (is 155). |
matches |
Evaluates to boolean true if the datamap on one side matches the pattern on the other. |
(dm:"Love",2,"Fear",4) matches (dm: "Love", num, "Fear", num) |
does not match |
Evaluates to boolean true if the datamap on one side does not match the pattern on the other. |
(dm:"Love",2,"Fear",4) matches (dm: "Joy", num, "Sorrow", num) |
is a , is an |
Evaluates to boolean true if the right side is a datatype that describes the left side. |
(dm:'a',1) is a datamap |
is not a , is not an |
Evaluates to boolean true if the right side is a datatype that does not describe the left side. |
(dm:'a',1) is not an empty |
Dataset data
Arrays are useful for dealing with a sequence of related data values, especially if
they have a particular order. There are occasions, however, where you don't really
care about the order, and instead would simply use the array as a storage place for
values - using contains
and is in
to check which values are inside.
Think of datasets as being like arrays, but with specific restrictions.
You can't access any positions within the dataset (so, for instance, the
1st
,2ndlast
andlast
aren't available, although thelength
still is) and can only usecontains
andis in
to see whether a value is inside (or, by usingsome
andall
, many values).Datasets only contain unique values: adding the string "Go" to a dataset already containing "Go" will do nothing. Values are considered unique if the
is
operator, when placed between them, would producefalse
.Datasets are considered equal (by the
is
operator) if they have the same items, regardless of order (as they have no order).
These restrictions can be helpful in that they can stop programming mistakes from occurring - you might accidentally try to modify a position in an array, but type the name of a different array that should not be modified as such. Using a dataset for the second array, if that is what best suits it, will cause an error to occur instead of allowing this unintended operation to continue.
Operator | Purpose | Example |
---|---|---|
is |
Evaluates to boolean true if both sides contain equal items, otherwise false . |
(ds:1,2) is (ds 2,1) (is true) |
is not |
Evaluates to true if both sides differ in items. |
(ds:5,4) is not (ds:5) (is true) |
contains |
Evaluates to true if the left side contains the right side. |
(ds:"Ape") contains "Ape" (ds:(ds:99)) contains (ds:99) (ds: 1,2,3) contains all of (a:2,3) (ds: 1,2,3) contains some of (a:3,4) |
does not contain |
Evaluates to true if the left side does not contain the right side. |
(ds:"Ape") does not contain "Egg" |
is in |
Evaluates to true if the right side contains the left side. |
"Ape" is in (ds:"Ape") (a:3,4) is in (ds:1,2,3) |
is not in |
Evaluates to true if the right side does not contain the left side. |
"Hope" is not in (ds:"Famine","Plague","Pollution") |
+ |
Joins datasets. | (ds:1,2,3) + (ds:1,2,4) (is (ds:1,2,3,4) ) |
- |
Subtracts datasets. | (ds:1,2,3) - (ds:1,3) (is (ds:2) ) |
... |
When used in a macro call, it separates each value in the right side. The dataset's values are sorted before they are spread out. |
(a: 0, ...(ds:1,2,3,4), 5) (is (a:0,1,2,3,4,5) ) |
matches |
Evaluates to boolean true if the dataset on one side matches the pattern on the other. |
(ds:2,3) matches (ds: 3, num) , (ds: array) matches (ds:(a: )) |
does not match |
Evaluates to boolean true if the dataset on one side does not match the pattern on the other. |
(ds:2,3) does not match (a: 3, str) , (ds: array) does not match (ds:2) |
is a , is an |
Evaluates to boolean true if the right side is a datatype that describes the left side. |
(ds:2,3) is a dataset |
is not a , is not an |
Evaluates to boolean true if the right side is a datatype that does not describe the left side. |
(ds:2,3) is an empty |
Datatype data
Datatypes are special keyword values used to confirm that a variable's data is a certain type - for instance, that a variable that should only hold a number does indeed
do so. They can be used to perform one-off checks using the is a
and matches
operators, and can be combined with variables to make TypedVars, variables that
are restricted to a certain type and that automatically perform these checks for you.
The built-in datatypes are as follows.
Value | Data type |
---|---|
number , num |
Numbers |
string , str |
Strings |
boolean , bool |
Booleans |
array |
Arrays |
datamap , dm |
Datamaps |
dataset , ds |
Datasets |
command |
Commands |
changer |
Changers |
color , colour |
Colours |
gradient |
Gradients |
lambda |
Lambdas |
macro |
CustomMacros |
datatype |
Datatypes |
codehook |
CodeHooks |
In addition to the above, there are a few variations of these that only match a certain subset of each type.
Value | Data type |
---|---|
even |
Only matches even numbers. |
odd |
Only matches odd numbers. |
integer , int |
Only matches whole numbers (numbers with no fractional component, and which are positive or negative). |
empty |
Only matches these empty structures: "" (the empty string), (a:) , (dm:) and (ds:) . |
whitespace |
Only matches a single character of whitespace (spaces, newlines, and other kinds of space). |
lowercase |
Only matches a single lowercase character. Lowercase characters are characters that change when put through (uppercase:). |
uppercase |
Only matches a single uppercase character. Uppercase characters are characters that change when put through (lowercase:). |
anycase |
This matches any character which is case-sensitive - that is, where its (lowercase:) form doesn't match its (uppercase:) form. |
alphanumeric , alnum |
Only matches a single alphanumeric character (letters and numbers). |
digit |
Only matches a string consisting of exactly one of the characters '0', '1', '2', '3', '4', '5', '6', '7', '8', and '9'. |
linebreak , newline |
Only matches a line break character (also known as a "newline" character). |
const |
Matches nothing; Use this only with (set:) to make constants. |
any |
Matches anything; Use this with (macro:) to make variables that accept any storable type, or with (set:) inside data structure patterns. |
Finally, custom string datatypes can be created using a suite of macros, starting with (p:). If any of the string datatypes above aren't exactly suited to the task you need them to perform, consider using (p:) to create your own.
If you want to check if a variable's data is a certain type, then you can use the is a
operator to do the comparison. To check if the data in $money is a number, write $money is a num
.
Warning: you must write is a
- don't write $money is num
, because is
by itself checks if the left side exactly equals the right side, and num
represents all numbers,
not the specific number contained in $money.
Note that data that can't be stored in a variable, such as HookNames, doesn't have a corresponding datatype name, because you won't need to compare things to it.
Additionally, along with the is a
operator, there is a matches
operator which is useful when you're dealing with data structures like arrays or datamaps. $pos is a array
checks if $pos is an
array, but that may not be precise enough for you. $pos matches (a: number, number)
checks to see if $pos is an array containing only two numbers in a row. A data structure with datatype
names in various positions inside it is called a pattern, and matches
is used to compare data values and patterns.
Spread datatypes:
A modified datatype can be created using the ...
syntax inside an (a:) macro call or a pattern macro call. ...str
can match any number of string values, including zero.
You can think of this as a counterpart to the normal use of the spread ...
syntax - just as one array is turned into many values, so too is ...str
considered equivalent to enough str
datatypes
to match the values on the other side.
Inside a typed variable in a custom macro call defined using (macro:)) a spread datatype refers to zero or more matching values being given at and after that variable's position in the macro call.
The typed variable in (macro: ...num-type _a, [(output:_a)])
refers to any number of numbers being given to the macro. The variable inside the code hook is an array containing all of those number values.
See the (macro:) macro's article for more details.
Inside a string pattern, like those created by (p:), spread datatypes have a slightly different meaning. They refer to zero or more sequences of the given datatype. ...whitespace
inside (p:) matches
an entire string of whitespace, which can be one or more characters, as well as the empty string. ...digit
matches zero or more digit characters.
Note: outside of macro calls and typed variables, spread datatypes are considered the same as normal datatypes. So, (a:2,3) matches ...num
produces false
- in this case, you should write (a:2,3) matches (a:...num)
instead.
Some more examples.
(datamap:'a',2,'b',4) matches (datamap:'b',num,'a',num))
is true.(a: 2, 3, 4) matches (a: 2, int, int)
is true. (Patterns can have exact values in them, which must be equal in the matching data).(a: ...num, ...str) matches (a: 2, 3, 4, 'five')
(a: (a: 2), (a: 4)) matches (a: (a: num), (a: even))
is true.(p: (p-many:"A"), "!") matches "AAAAAAAA!"
is true.
To summarise, the datatype operators are the following.
Operator | Purpose | Example |
---|---|---|
matches |
Evaluates to boolean true if the data on the left matches the pattern on the right. |
(a:2,3) matches (a: num, num) |
is a , is an |
Similar to matches , but requires the right side to be just a type name. |
(a:2,3) is an array , 4.1 is a number |
-type |
Produces a TypedVar, if a variable follows it. Note that there can't be any space between - and type . |
num-type $funds |
... |
Produces a spread datatype, which, when used in arrays or patterns, matches zero or more values that match the type. | (a: ...str) matches (a:'Elf','Drow','Kithkin') |
Gradient data
Gradients are special composite data values which can be provided to (bg:) and (meter:). Much as colour data represents a single colour that can be used with (text-colour:) or (bg:), gradients represent special computer-generated backgrounds that smoothly transition from one colour on one side, to another colour on its other side, with any number of other colours in between.
For an in-depth description of how to create gradients, see the article for (gradient:).
Gradients, like colours, have a few data values you can examine, but cannot (set:).
Data name | Example | Meaning |
---|---|---|
angle |
$grad's angle |
The angle, a number from 0 to 359. If a value outside this range was given to (gradient:) , this will return that value wrapped to this range. |
stops |
$grad's stops |
An array of the gradient's colour-stops, represented as datamaps. Gradients created by (gradient:) have "percent" and "colour" data names in this datamap. For (stripes:) gradients, "pixels" and "colour" data names appear instead. (gradient: 90, 0.2, blue, 0.8, white)'s stops is (a:(dm: "percent", 0.2, "colour", blue), (dm: "percent", 0.8, "colour", white)) . |
Gradients, when used in passage prose or given to (print:), produce a square swatch containing the gradient. This is a <tw-colour>
element, but otherwise has no other features or capabilities and is intended solely for debugging purposes.
HookName data
A hook name is like a variable name, but with ?
replacing the $
sigil. When given to a macro that accepts it,
it signifies that all hooks with the given name should be affected by the macro.
For instance, (click: ?red)
will cause all hooks with a <red|
or |red>
nametag to be subject to the (click:)
macro's behaviour.
In earlier Harlowe versions, it was possible to also use hook names with (set:), (put:) and (move:) to modify the text of the hooks, but macros such as (replace:) should be used to accomplish this instead.
If a hook name does not apply to a single hook in the given passage (for instance, if you type ?rde
instead of
?red
) then no error will be produced. This is to allow macros such as (click:) to be placed in the header
or footer
passages, and thus easily affect hooks in every passage, even if individual passages lack the given hook name. Of course, it
means that you'll have to be extra careful while typing the hook name, as misspellings will not be easily identified
by Harlowe itself.
If you wish to construct a hook name programmatically, based on an existing string variable, then the (hooks-named:) macro may be used in place of the above syntax.
Built in hook names:
There are five special built-in hook names, ?Page, ?Passage, ?Sidebar, ?Link and ?Visited, which, in addition to selecting named hooks, also affect parts of the page that you can't normally style with macros. They can be styled using the (enchant:) macro.
?Page
selects the page element (to be precise, the<tw-story>
element) and using it with the (background:) macro lets you change the background of the entire page.?Passage
affects just the element that contains the current passage's text (to be precise, the<tw-passage>
element) and lets you, for instance, change the (text-colour:) or (font:) of all the text, or apply complex (css:) to it.?Sidebar
selects the passage's sidebar containing undo/redo icons (<tw-sidebar>
). You can style it with styling macros, or use (replace:) or (append:) to insert your own text into it.?Link
selects all of the links (passage links, and those created by (link:) and other macros) in the passage.
Data names:
If you only want some of the hooks with the given name to be affected, you can treat the hook name as a sort of read-only
array: specify just its 1st
element (such as by ?red's 1st
) to only affect the first such named hook in the passage, access
the last
to affect the last, and so forth. You can also specify multiple elements, using syntax like 1stto3rd
, to affect all of
the elements between and including those positions. Even specifying an array of arbitrary positions, like ?red's (a:1,3,7)
, will work.
Unlike arrays, though, you can't access their length
, nor can you spread them with ...
.
Moreover, a few special data names exist.
chars
(as in?title's chars
) selects each individual character inside the hook, as if it was in its own hook. This can be used for a variety of text effects - using (enchant:) with?hook's chars's 1st
can be used to give a hook a styled "drop-cap" without having to explicitly style the leading character. This will NOT select whitespace characters.lines
(as in?passage's lines
) selects individual lines of text within a hook. A line is any run of text or code between line breaks (or the passage's start and end) - a word-wrapped paragraph of prose is thus considered a single "line" as a result.links
(as in?body's links
) is similar in use to?link
, but only selects links within the hook.visited
(as in?body's visited
) only selects links to visited passages. This allows you to affect and re-style these links in particular. These links normally have a different colour to normal links, but if you apply a colour to?Link
, that colour is replaced. You can restore the distinct colour these links have by applying a colour tovisited
afterward.
Warning: using chars
with (enchant:) may cause text-to-speech assistive programs to fail to read the enchanted
passage correctly. If this would be unacceptable for you or your story, refrain from using chars
with (enchant:).
Warning: using chars
with (enchant:) to enchant very large amounts of text at once will likely cause excessive CPU load for the reader,
making their browser unresponsive.
Data name | Example | Meaning |
---|---|---|
1st ,2nd ,last , etc. |
?hook's last , 1st of ?hook |
Only one hook with the given name, at a certain position in the passage relative to the rest (first with its name, last with its name, etc). |
1stto3rd , 4thlastto2ndlast etc. |
?hook's 2ndto5th |
Only hooks with this name, at a certain sequence of positions (such as the first, second and third for 1stto3rd ) relative to the rest. |
chars |
?title's chars , chars of ?scream |
Each individual character within the hook, as if the characters were hooks in themselves. |
links |
?body's links , links of ?aside |
Each link inside the hook. |
lines |
?passage's lines , lines of ?passage |
Each span of continuous text, separated by line breaks, inside the passage. |
visited |
?passage's visited , visited of ?passage |
Each passage link (and (link-goto:) link) inside the hook that points to an already visited passage. |
Operators:
Unlike most forms of data, only one operator can be used with hook names.
Operator | Purpose | Example |
---|---|---|
+ |
Creates a hook name that, when given to macros, causes both of the added hooks to be affected by that macro. | (click: ?red + ?blue's 1st) affects all hooks tagged red , as well as the first hook tagged blue . |
Instant data
A few special macros in Harlowe perform actions immediately, as soon as they're evaluated. These can be used in passages, but cannot have their values saved using (set:) or (put:), or stored in data structures.
Lambda data
Suppose you want to do a complicated task with an array, like, say, convert all of its strings to lowercase, or check if its datamaps have "health" data equal to 0, or join all of its strings together into a single string. You want to be able to tell Harlowe to search for "each string where the string's 1st letter is A". You want to write a "function" for how the search is to be conducted.
Lambdas are user-created functions that let you tell certain macros, like (find:), (altered:) and (folded:), precisely how to search, alter, or combine the data provided to them. Try thinking of them as stored expressions that operate on a single value in the sequence of data, which are then run on every value in turn.
There are several types of lambdas, as well as lambdas that comprise multiple types.
"where" lambdas, used by the (find:) macro, are used to search for and filter data. The lambda
_item where _item's 1st is "A"
tells the macro to searches for items whose1st
is the string "A".For certain macros, like (for:), you may want to use a "where" lambda that doesn't filter out any of the values -
_item where true
, for instance, will include every item. There is a special, more readable "each" shorthand for this type of "where" lambda: writing justeach _item
is equivalent."when" lambdas are a variant of "where" used exclusively by (event:) and (storylet:), and are used to specify when a hook or passage should be shown. The lambda
when $fuel > 8
tells (event:) to show the attached hook when$fuel
is increased (due to an interaction macro like (link-repeat:), a (live:) macro, or anything else). This really shouln't be called a "lambda", but you can perhaps think of it in terms of it filtering moments in time that pass or fail the condition."via" lambdas, used by the (altered:) and (sorted:) macro, are used to translate or change data. The lambda
_item via _item + "s"
tells the macro to add the string "s" to the end of each item. Only the (altered:) macro uses this to change values: the (sorted:) macro uses this to sort the data value as if it was another value."making" lambdas, used by the (folded:) are used to build or "make" a single data value by adding something from each item to it. The lambda
_item making _total via _total + (max: _item, 0)
tells the macro to add each item to the total, but only if the item is greater than 0. (Incidentally, you can also use "where" inside a "making" lambda - you could rewrite that lambda as_item making _total via _total + _item where _item > 0
.)
Lambdas use temp variables to hold the actual values. For instance, in (find: _num where _num > 2, 5,6,0)
,
the temp variable _num
is used to mean each individual value given to the macro, in turn. It will be 5, 6 and 0, respectively.
Importantly, this will not alter any existing temp variable called _num
- the inside of a lambda can be thought
of as a hook, so just as the inner _x
in (set: _x to 1) |a>[ (set:_x to 2) ]
is different from the outer _x
, the _num
in the
lambda will not affect any other _num
.
If you want to be extra watchful for errors and mistyped data (and if you're working on a big project, you should!), you can include a datatype
with each variable, such as by writing str-type _a where _a contains 'e'
instead of _a where _a contains 'e'
, to make it a TypedVar.
You may notice that _a contains 'e'
would also be true if _a
was (a:'e')
rather than, as intended, a string. Adding str-type
allows such an error to be found and reported early, and results in a less confusing error message.
You can use the "it" shorthand to save on having to write the temporary variable's name multiple times.
_num where _num > 2
can be rewritten as_num where it > 2
. Not only that, but you can even save on naming the temporary
variable at all, by just using where
(or via
or making
) without the name and only using it
to refer to the variable: where it > 2
.
Additionally, the "pos" identifier can be used inside a lambda - it evaluates to the position of the data value (from those passed into
the macro) that the lambda is currently processing. (altered: via it + (str:pos), "A", "B", "C", "A")
produces (a:"A1","B2","C3","A4")
.
An important feature is that you can save lambdas into variables, and reuse them in your story easily. You
could, for instance, (set: $statsReadout to (_stat making _readout via _readout + "|" + _stat's name + ":" + _stat's value))
,
and then use $printStats with the (folded:) macro in different places, such as (folded: $statsReadout, ...(dm-entries: $playerStats))
for displaying the player's stats, (folded: $statsReadout, ...(dm-entries: $monsterStats))
for a monster's stats, etc.
Lambdas are named after the lambda calculus, and the "lambda" keyword used in many popular programming languages. They may seem complicated, but as long as you think of them as just a special way of writing a repeating instruction, and understand how their macros work, you may find that they are very convenient.
Metadata data
Certain kinds of macros are not used inside the passage itself, but are used to mark the passage as being special in some way, or having certain data available to other macros that inspect the story's state, such as (passages:) or (open-storylets:). These macros are "metadata" macros, because they attach metadata to the passage. These macros must appear at the very start of those passages, ahead of every other kind of macro. Using them in any other location will cause an error.
Every valid metadata macro is run only once, when the story begins. As such, "metadata" is not a type of data that's available to other macros,
and there is no need for a datatype called metadata
.
Number data
Number data is just numbers, which you can perform basic mathematical calculations with. You'll generally use numbers to keep track of statistics for characters, count how many times an event has occurred, and numerous other uses.
You can do all the basic mathematical operations you'd expect to numbers:
(1 + 2) / 0.25 + (3 + 2) * 0.2
evaluates to the number 13. The computer follows the normal order of
operations in mathematics: first multiplying and dividing, then adding and subtracting. You can group
subexpressions together and force them to be evaluated first with parentheses.
If you're not familiar with some of those symbols, here's a review, along with various other operations you can perform.
Operator | Function | Example |
---|---|---|
+ |
Addition. | 5 + 5 (is 10) |
- |
Subtraction. Can also be used to negate a number. | 5 - -5 (is 10) |
* |
Multiplication. | 5 * 5 (is 25) |
/ |
Division. | 5 / 5 (is 1) |
% |
Modulo (remainder of a division). | 26 % 5 (is 1) |
> |
Evaluates to boolean true if the left side is greater than the right side, otherwise false . |
$money > 3.75 |
>= |
Evaluates to boolean true if the left side is greater than or equal to the right side, otherwise false . |
$apples >= $carrots + 5 |
< |
Evaluates to boolean true if the left side is less than the right side, otherwise false . |
$shoes < $people * 2 |
<= |
Evaluates to boolean true if the left side is less than or equal to the right side, otherwise false . |
65 <= $age |
is |
Evaluates to boolean true if both sides are equal, otherwise false . |
$agendaPoints is 2 |
is not |
Evaluates to boolean true if both sides are not equal, otherwise false . |
$agendaPoints is not 0 |
matches |
Evaluates to boolean true if one side describes the other. |
$bytes matches 165 , odd matches 3 |
does not match |
Evaluates to boolean true if one side does not describe the other. |
$coins does not match odd |
is a , is an |
Evaluates to boolean true if the right side is a datatype describing the left side |
$credits is a num , 2 is an even |
is not a , is not an |
Evaluates to boolean true if the right side does not describe the left side. |
0 is not an odd , 13 is not an even |
+
can also be used by itself to check if a variable is a number: +$result
produces an error if $result is not a number.
You can only perform these operations (apart from is
) on two pieces of data if they're both numbers. Adding the
string "5" to the number 2 would produce an error, and not the number 7 nor the string "52". You must
convert one side or the other using the (num:) or (str:) macros.
Finally, certain macros that accept numbers, such as (live:)
, use those numbers as time durations. There is a special form of
number data you can use for this – put "s" or "ms" at the end of the number to specify if the number indicates milliseconds or
seconds. For instance, 50ms
means 50 milliseconds, and 5s
means 5 seconds (which is 5000 milliseconds). A number suffixed with s
is
the same as a number suffixed with ms
and multiplied by 1000.
String data
A string is just a run of text - a sequence of text characters strung together. To indicate that a
sequence of characters is a string, place a matching pair of either "
or '
characters around them. If you want
to include a "
or '
inside a string that is enclosed with a pair of that character, you can use the \
symbol to
escape that character. \"
and \"
will become a "
and '
, respectively.
If you want to include a \
character by itself, write \\
.
When the \
character precedes the letters n
, t
, b
, f
, v
, r
, they will both be replaced with a certain whitespace character. Except for \n
, these are
not intended to be used by authors for any reason, are included purely for compatibility with Javascript, and are listed here for reference.
Combination | Result | Example |
---|---|---|
\n |
A newline (also known as a line break) | |
\t |
A tab character (normally only the same size as a single space) | |
\b , \f , \v , \r |
A zero-width character that takes up a position in the string but isn't visible in the passage (included for compatibility with Javascript) | |
\x |
If the next two characters are hexadecimal digits (0-9, A-F, or a-f), all four of these are replaced with a character whose Unicode code point is the value of the digits. | "\xFE" (is "þ" ) |
\u |
If the next four characters are hexadecimal digits (0-9, A-F, or a-f), OR if the next characters are { , one to five hexadecimal digits, and } , then all of these are replaced with a character whose Unicode code point is the value of the digits. |
"\u00FE" (is "þ" ), "\u{1F494}" (is "💔" ) |
Note that you don't have to use \n
to encode line breaks inside strings. You can simply insert them directly, thus causing the strings to span multiple lines.
However, you may wish to use \n
sometimes to save vertical space in your passage code.
When making a story, you'll mostly work with strings that you intend to insert into
the passage source. If a string contains markup, then the markup will be processed when it's
inserted. For instance, "The ''biiiiig'' bellyblob"
will print as "The biiiiig bellyblob".
Even macro calls inside strings will be processed: printing "The (print:2*3) bears"
will print "The 6 bears".
If you wish to avoid this, you can include the verbatim markup inside the string:"`It's (exactly: as planned)`"
will
print "It's (exactly: as planned)". Alternatively, you can use (verbatim-print:) to prevent the markup from being processed.
You can add strings together to join them: "The" + ' former ' + "Prime Minister's"
pushes the strings together, and evaluates to "The former Prime Minister's". Notice
that spaces had to be added between the words in order to produce a properly spaced final string.
Also, notice that you can only add strings together. You can't subtract them, much less multiply or divide them.
Strings are similar to arrays, in that their individual characters can be accessed: "ABC"'s 1st
evaluates to "A",
"Gosh"'s 2ndlast
evaluates to "s", and "Exeunt"'s last
evaluates to "t". They, too, have a "length":
"Marathon"'s length
is 8. If you can't determine the exact position of a character, you can use an expression,
in brackets, after it: $string's ($pos - 3)
. You can create a substring by providing an array of positions
in place of a single position: "Dogs"'s (a: 2,4)
is "os". And, you can create a substring of consecutive positions by
specifying just the start and end position as a data name: "Ducks"'s 1stto3rd
is "Duc", and "Rags"'s 2ndlasttolast
is "gs".
If you want to check if a string contains any of another string's characters (without needing to be in the
same order), or all of them, special some
(also known as any
- no relation to the any
datatype) and all
data names are available for use with the is
, is not
, matches
and is a
operators - all of $name is "A"
checks if the variable consists only of capital "A"'s, and some of $name is a whitespace
checks
if any of the variable's characters is a whitespace character (using the special "whitespace" datatype).
You can use the contains
and is in
operators to see if a certain string is contained within another: "mother" contains "moth"
is true, as is "a" is in "a"
. Again, some
and all
can be used with contains
and is in
to check all their
characters - all of $string is not "w"
is true if the string doesn't contain "w", and $string contains some of "aeiou"
is true
if the string contains those five letters. The opposite of the is in
operator is is not in
- "w" is not in $string
is another way to phrase the above.
If you want to check if a string specifically starts or ends with with a certain substring, start
and end
data names can be used in a
similar way to some
and all
- start of $addr is "http://"
is the same as $addr's 1stto7th is "http://"
(but somewhat easier to write), and
end of $angelName is "iel"
is the same as $angelName's 3rdlasttolast is "iel"
.
Here is a table listing the aforementioned operations you can perform on strings, as well as a few others.
Operator | Function | Example |
---|---|---|
+ |
Joining. | "A" + "Z" (is "AZ") |
- |
Produces a copy of the left string with all occurrences of the right string removed. | "abcdcba" - "bcd" (is "acba") |
is |
Evaluates to boolean true if both sides are equal, otherwise false . |
$name is "Frederika" some of "Buxom" is "x" |
is not |
Evaluates to boolean true if both sides are not equal, otherwise false . |
$friends is not $enemies all of "Gadsby" is not "e" |
contains |
Evaluates to boolean true if the left side contains the right side, otherwise false . |
"Fear" contains "ear" |
does not contain |
Evaluates to boolean true if the left side does not contain the right side, otherwise false . |
"Fear" does not contain "Bee" |
is in |
Checking if the right string contains the left string, otherwise false . |
"ugh" is in "Through" |
is not in |
Evaluates to true if the right string does not contain the left string. |
"Blood" is not in "Stone |
's |
Obtains the character or substring at the right numeric position. 's cannot have any spaces to its left. |
"YO"'s 1st (is "Y")"PS"'s (2) (is "S")"ear"'s (a: 2,3) (is "ar") |
of |
Obtains the character at the left numeric position. | 1st of "YO" (is "Y")(2) of "PS" (is "S")(a: 2,3) of "ear" (is "ar") |
matches |
Evaluates to boolean true if the left side describes the right side. |
str matches "Contract" , some of "RED" matches "E" |
does not match |
Evaluates to boolean true if the left side does not describe the right side. |
str does not match "Minotaur" , "3" does not match "Three" |
is a , is an |
Evaluates to boolean true if the right side is a datatype describing the left side. |
"Boo" is a string , " " is a whitespace , "" is an empty |
is not a , is not an |
Evaluates to boolean true if the right side does not describe the left side. |
"Boo" is not an empty , "" is not a whitespace |
And, here are the data names that can be used with strings.
Data name | Example | Meaning |
---|---|---|
1st ,2nd ,last , etc. |
$str's last , 1st of $str |
A single character at the given position in the string. This causes an error if it passes the bounds of the string, such as "elder"'s 8th . |
1stto3rd , 4thlastto2ndlast etc. |
"aeiou"'s 2ndto5th |
A substring containing only the characters between the given positions (such as the first, second and third for 1stto3rd ). This does NOT cause an error if it passes the bounds of the string - so "Power"'s 3rdto9th is "wer" . |
length |
"Penny"'s length |
The length (number of characters) in the string. |
some , any , all |
all of "aeiou" is not "y" , some of "aaaba" is not "a" |
Usable only with comparison operators, these allow all of the characters to be quickly compared. any is an old alias for some that functions identically, but which may be removed in a future version of Harlowe. |
start , end |
start of $addr is "http://" , end of $angelName is "iel" |
Usable only with the is , is not , matches and does not match operators, these allow you to compare the start or end of strings without having to specify an exact range of characters to compare. |
random |
A random character in the string. | "aeiou"'s random (is "a" , "e" , "i" , "o" or "u" ). |
Arrays of numbers, such as (a:3,5) |
$str's (a:1,-1) |
A substring containing just the characters at the given positions in the string. |
A note about random
: this is one of the features that uses Harlowe's pseudo-random number generator. If you use (seed:) at the start of the story,
the selected values will be predetermined based on the seed string, and how many other random macros and features have been used before it.
TypedVar data
Typed variable names combine a datatype or a pattern of data, and the name of a variable, joined by adding the -type
suffix to the datatype. str-type _name
defines
a typed variable, _name, which can only be set to a string. (a: num)-type $a
defines a typed variable, $a, which can only be set to an array with 1 number value inside.
Typed variable names are used in several places – (set:), (put:), (move:) and (unpack:) can be given typed variables in place of normal variables to restrict that variable to the given type, and ensure all future uses of that variable maintain that restriction. Typed variables are also used by the (macro:) macro to specify the input data for your custom macros, and ensure users of that macro maintain those restrictions. The (str-find:) and (str-replaced:) macros allows typed variables to be used inside string patterns, to give names to sub-matches in the string being searched. Finally, typed temp variables can be used in lambdas, to guarantee that the lambda is being used with the correct type of data.
The ability to restrict the type of data that your variables and custom macros receive is a great assistance in debugging your stories, as well as understanding what the variable or macro is and does - especially if they were written by someone else and imported into the project.
TypedVars used with the (macro:) macro support an additional feature. When a TypedVar is a plain datatype name preceded with the ...
spread operator, such as in ...str-type _a
,
then it becomes a spread typed variable, which represents an arbitrary number of values. Giving multiple values of the given type at or after such a position will cause an array
containing those values to be put into the named variable.
Typed variables, when retrieved from a custom macro's "params" array, have two data names that you can examine.
Data name | Example | Meaning |
---|---|---|
name |
$customMac's params's 1st's name , name of 1st of params of $customMac |
The name of the typed variable. (num-type _grains)'s name is "grains" . |
datatype |
$customMac's params's 1st's datatype , datatype of 1st of params of $customMac |
The datatype of the typed variable. (num-type _grains)'s datatype is num . |
For more details, consult the (set:) and (macro:) articles.
VariableToValue data
This is a special value that only (set:), (put:) and (unpack:) make use of.
It's created by joining a value and a variable (or a TypedVar, or data structure containing TypedVars) with the to
or into
keywords:
$emotion to 'flustered'
is an example of a VariableToValue. It exists primarily to make (set:) and (put:) more readable.
Change log
4.0.0 changes (unreleased)
This version is previewable with unstable builds.
Bugfixes
Macros
- Fixed a bug where the order of debug notifier messages for an
(unpack:)
call was sometimes wrong, or missing one message. - Fixed a bug where
(else:)
changers saved into variables couldn't be reloaded from save files. (Note that there isn't really much point to saving an(else:)
changer to a variable, though.) - Fixed a bug where
(find:)
would incorrectly error when the lambda used theit
identifier, and the second value given to it (the value immediately after the lambda) was""
,0
, orfalse
. - Fixed a bug where colours created using
(lch:)
didn't work properly with theis
operator. - Fixed a bug where producing unstorable values (such as hook names) in the
via
clause of an(altered:)
,(folded:)
or(dm-altered:)
lambda, such as(altered: via ?hook, 1)
, wouldn't produce an error. - Fixed a bug where it was possible for
(data-pattern:)
to produce a datamap with an error value inside it, instead of simply producing the error. - Fixed a bug where nonexistant temp variable errors produced in
(meter:)
or(icon-counter:)
calls weren't being displayed correctly. - Fixed a bug where
(scroll:)
still didn't work when given?page
and a percentage, unless<body>
had been givenoverflow-y: scroll
. - Fixed a bug where certain changers, such as
(for:)
, wouldn't work when combined with(output:)
(such as in(macro: [(for: each _a, 1,2,3,4,5)+(out:)[_a]]))
). - Fixed a bug where keywords such as
it
would be displayed incorrectly in error messages or debug replays, such as caused by erroneous code like(set:$a to bind it)
. - Fixed an aforementioned bug where unclosed hooks
[=
in headers, the main passage, or footers can "punch through" and enclose prose in the main passage. (For compatibility reasons, this was left unfixed in 3.3.) - Fixed a bug where comparison operators like < and > wouldn't error if no values were on the left side, such as in
(print: < 4)
(instead inferring a 0 on the left side). - Fixed a bug where the addition and subtraction operators wouldn't error if no values were on the right side, such as in
(print: 2 + )
.
Other
- Fixed a bug where debug replays involving certain kinds of built-in data names, such as a colour's
r
, would display invalid code in one step of the replay.
Alterations
Coding
- Now, story-wide variables (like
$goo
) no longer have a default value of 0 when first used - they must be explicitly created using(set:)
,(put:)
,(unpack:)
, abind
-using macro, or so forth. This unifies the behaviour of story-wide and temp variables.- As a result of this, calling a storylet-checking macro like
(open-storylets:)
will now error if any storylet condition accesses a story-wide variable that hasn't been created yet.
- As a result of this, calling a storylet-checking macro like
- Now, the
's
syntax can permit spaces between it and the variable, such as in(set: $a 's 1st to 0)
. - Increased the maximum acceptable C value given to
(lch:)
from 132 to 150. - Colours now have an
oklch
datamap value, acessible as_colour's oklch
. This is similar to thelch
datamap in that it hasl
,c
andh
number data values, and corresponds to the addition of the new(oklch:)
macro, below. This can be used to obtain OKLCH values for colours that were defined using RGB (as in the(rgb:)
and(hsl:)
macros, or raw hexadecimal colours like#33a9f4
). - Changed the method by which two colour are considered equal by Harlowe (for the
is
andis not
operator). Now, they are considered equal if their data values are all within 0.01 of each other, rather than being exactly equal. This is to work around very slightly imprecise conversions between RGB/HSL and the LCH and OKLCH colour models. - Eliminated the "Instant" data type, which in Harlowe 3 was used for
(set:)
,(unpack:)
and similar macros. Now, those macros produce commands, but those commands explicitly can't be stored in variables. This should have no meaningful difference in how you'd typically write code, but makes the documentation a bit cleaner.- This also fixes a bug in which said macros didn't display correctly in debug replays.
Macros
(bg:)
now has looser checks to determine whether a given string value represents a CSS colour value or not, to support passing in recent CSS colour functions likecolor-mix()
to it.(bg:)
now accepts CSS gradient strings likeconic-gradient()
in addition to Harlowe gradients. (In previous versions, it also acceptedlinear-gradient()
strings, but this was undocumented.)(lch:)
colours are no longer converted to RGBA when used, instead using the modern CSSlch()
function.(complement:)
now internally uses the OKLCH colour model to create the complement, instead of LCH.
Errors
- Now, using the spread syntax on invalid values (like numbers) will produce a more instructive error message in more cases.
- Improved the error message for the case where
(sorted:)
's lambda didn't produce a string or number.
Compatibility
- Removed support for Internet Explorer 10. So it goes. Official support is now "browsers with >0.2% usage on Browserslist.dev" (minus a few special cases), which is the same support as in-browser Twine.
- Note 1: Electron support is based on Google Chrome support, so it should be supported as well.
- Note 2: Full support for the older edge of this browser range will only be confirmed near the end of 4.0 development, so unstable builds may have much narrower support until then.
- Removed the 'any' data name for arrays and datasets (which had been renamed to 'some' in 3.3.0) due to confusion with the
any
datatype -(if: $a is any of $b)
must now be written as(if: $a is some of $b)
, or preferably as(if: some of $b is $a)
.
Additions
Macros
- Added
(define:)
, a metadata version of(set:)
that only accepts story-wide variables (using$
). Variables created using(define:)
are likeconst-type
variables, except that they are created when the story begins (NOT when their passage is actually visited), and their values are not saved in save files. This means that editing those variables' data in the editor is much less likely to invalidate save files made during testing - a common difficulty when using ordinary story-wide variables, especially when they hold complicated values like custom macros.(define:)
is also primarily designed for use with image assets (when those are implemented), as Harlowe, when running these macro calls on startup, will be able to preload the images immediately (and thus, the author doesn't have to explicitly load all of them in the starting passage or a header passage). - Added
(oklch:)
, a colour macro comparable to(lch:)
, which maps to theoklch()
function in CSS.
3.3.9 changes (Jun 16, 2024)
- Fixed a bug where
(scroll:)
still didn't work when given?page
and a percentage, unless<body>
had been givenoverflow-y: scroll
. (This is the same bugfix as in 4.0.0).
3.3.8 changes (Jan 03, 2024)
Bugfixes
- Fixed a bug where a spread parameter of a custom macro (such as
_b
in(macro: any-type _a, ...any-type _b)
) wouldn't produce an empty array if no values were given to it, but values were given to all the previous parameters. - To ameliorate a long-standing bug involving
(t8n-depart:)
messing up the widths of block elements (such as(box:)
-enchanted hooks) inside the departing passage as it transitions (due to giving the departing<tw-passage>
the CSS propertyposition:absolute
), the departing<tw-passage>
element now also haswidth:100%
applied to it, unless it was already given another width by story CSS,<style>
elements,(enchant:)
, or so forth.
A note about the 3.3.5 save file bug: due to repeated inability to replicate it, I've instead analysed the bug from above, and decided that it's probably caused by inherent instability/unreliability of the localStorage API. Thus, I believe it will be solved by rewriting the save file code to use a different API, such as IndexedDB. However, because this will break backwards-compatibility, this change will only arrive in 4.0. The next version is also likely to bring more save-file-related macros (such as ones that allow them to be exported to downloadable files) that may provide authors more flexibility.
3.3.7 changes (27 Aug, 2023)
Bugfixes
- Fixed a long-standing bug where
(str-nth:)
would produce"11st"
and"12nd"
when given11
and12
, respectively (as well as"111st"
for111
, and so on). - Editor: Fixed a bug where the completion menu's keyboard controls were not being properly disposed of after the menu was dismissed, causing certain keystrokes (up arrow, down arrow, Enter, Escape, etc.) to be blocked until the editor was closed.
Alterations
Editor
- Now, the Find/Replace panel supports
$1
,$2
,$3
, etc. in the "Replace With" box as a way to access regular expression capturing groups. (This technically already existed as an undocumented feature, but only worked with "Replace All".) - Now, the Find/Replace panel's contents will no longer be cleared whenever it is closed, forcing queries to be re-typed each time. (Note that opening it with text selected will still replace the query with the selection.)
3.3.6 changes (Jul 07, 2023)
Note: due to difficulties in reproducing the bug, the save file issue mentioned in 3.3.5 changes has not yet been resolved, and the story tag features have not been removed in this version.
Bugfixes
Coding
- Fixed a long-standing bug where the
's
syntax couldn't be used with a single temp variable on the right side (such as$a's _b
) unless the temp variable had brackets around it (such as$a's (_b)
) even though theof
syntax works fine with them (in cases like_b of $a
). - Fixed a long-standing bug where a very large number of values given to a lambda-using macro like
(for:)
or(find:)
(such as(for: each _e, ...(range:0, 1000000))
) would cause a Javascript error. - Fixed a bug where
(link-storylet:)
's optional final string would, if given, override and disable the link even if there was an available storylet to link to.- Also added missing
(link-storylet:)
documentation for the optional first string, which provides link text to use in place of the storylet's passage name. This feature was formerly only indicated by certain error messages.
- Also added missing
(align:)
hooks no longer have the CSS propertydisplay:inline-block
instead ofdisplay:block
, which caused their margins to be ignored.(box:)
hooks no longer have the CSS propertybox-sizing:content-box
, which caused, among other issues, full-length boxes (created by(box:"X")
) to slightly jut out of their containing passages or columns.- Note that if the preceding fixes cause problems for you, you can resolve them by including the
(css:)
changer to reinstate the properties, such as(css:"display:block")
or(css:"box-sizing:content-box")
.
- Note that if the preceding fixes cause problems for you, you can resolve them by including the
Editor
- Pressing the Escape key when a toolbar panel, tooltip, or completion popup menu is present will now close the panel, tooltip or menu without closing the entire Passage Editor dialog.
- Now, the keyword completion popup menu will no longer appear after space characters, but only after the first character of a possible keyword.
- Fixed a bug where the dialogs that appear from certain tooltip buttons are hidden behind other elements when using Twine 2.6's "dialog stack" feature to open multiple passage editors.
- Fixed a bug where the Find/Replace panel's "Only in selection" option wouldn't find any results even if one was selected.
Alterations
Coding
- The
(box:)
height value now corresponds to a CSS height of 1.5em (Harlowe's default CSSline-height
) multiplied by the given number, plus the default vertical padding of(box:)
hooks (2em), thus more closely corresponding to the height of an individual line. Formerly, the presence ofbox-sizing:content-box
(see above) meant that this vertical padding didn't need to be added, but now it is manually applied. As a result, this will not adjust to match any custom CSS padding given to this hook in addition to(box:)
. - Now, when a
where
clause is given to(folded:)
's lambda, such as in_a making _b via _b + _a where _a's length > 5
, it no longer applies to the first value - because, logically, that value is put directly into_b
and should never be an_a
value. This makeswhere
clauses less awkward when the(folded:)
result value's datatype is different from the input values' datatype (for instance, building a string from an array of datamaps).- Note that if the following change causes problems for you, you can resolve them by adding a first value to your
(folded:)
calls which won't affect the total - for instance, if the(folded:)
call builds a string, adding an empty string as the first value.
- Note that if the following change causes problems for you, you can resolve them by adding a first value to your
Editor
- Now, the Harlowe toolbar will save the state of the "Coding tooltips" button between sessions, so that it will remain off when you reopen Twine if you switched it off previously.
- Added a Harlowe-specific editor preferences panel. These preferences are also saved between Twine sessions. Currently, the following preferences can be changed:
- Toggling the completion popup menus (macro completions and keyword completions) on and off.
- Toggling whether macro code uses the defined Code Editor font (in Twine's preferences) or not. (In 3.3.5 and earlier, it would always use the monospaced font regardless.)
- Altered the Find/Replace panel to more closely resemble the Twine 2.6 "Find/Replace in Story" dialog.
- Added a "Use Regular Expressions" checkbox to the Find/Replace panel, to match the "Find/Replace in Story" dialog.
3.3.5 changes (Feb 27, 2023)
Bugfixes
- Debug Mode: Fixed a performance issue where large numbers of
(source:)
buttons in the Variables Panel (and the rows they would reveal when clicked) would cause noticeable slowdown. - Debug Mode: Also added some smaller optimisations to the Variables Panel.
Alterations
- Based on a number of reports about the "pure variable" save file optimisation feature in 3.3.0, there are still lingering and difficult-to-replicate saving issues. As a temporary bug triage measure, I have added the following feature:
- Giving your story the story tag
uncompressed-pure-values
(using the "Tag" feature in the Twine story library) will disable the optimisation that saves "pure values" as references to passage prose. - Giving your story the story tag
uncompressed-structures
will disable the optimisation that saves modified data structures as differences from previous turns (using theit
identifier). (Note: this only applies when saving a game that has an undo cache - using(forget-undos:)
to forget all previous turns will also force data structures to be saved uncompressed.) - Giving your story the story tag
uncompressed-saves
will have the effect of both of the above tags.
- Giving your story the story tag
- Please note that this is an unintended feature meant only for alleviating save file corruption in existing stories, and will be removed in future versions once the saving issues have been resolved. I am terrible sorry for the trouble that this has caused, and will work hard to solve it with the help of your reports.
3.3.4 changes (Jan 09, 2023)
Bugfixes
- Fixed a long-standing bug where incomplete macro calls like
(set:
couldn't appear inside the HTML comment syntax. - Fixed a bug where, when using the 3.3.0 "special case" allowing unclosed hooks in headers, the main passage, or footers to still "punch through" to affect things that appear after, passage data would become corrupted, causing punched-through code (such as footers) to be duplicated every time the passage was visited, even while undoing turns, until the story was reloaded.
- Fixed a bug where code hooks couldn't contain a
)
symbol as part of its bare prose (not part of a macro call or a data value). - Fixed a bug where the 3.3.0 "hook transition judder" fix wouldn't properly apply to hooks containing hidden HTML elements (such as commands).
- Fixed a bug where
(scroll:)
didn't work correctly with?page
if the<tw-story>
element didn't have a fixed height (via CSS). - Debug Mode: fixed a bug where the variables pane would simply not update if variables were being updated (by (live:) or another such macro) every 300ms or less.
- Fixed a syntax highlighter bug where the underline for the cursor's nearest syntax token would, if it spanned multiple lines, sometimes incorrectly persist after editing.
- Fixed a syntax highlighter bug where, when "Proofreading View" was enabled, text inside the verbatim markup would be incorrectly dimmed.
- Editor: The listed keyboard shortcuts for various toolbar operations should now correctly use the ⌘ key on macOS.
Alterations
Saving
- Now, variables saved as an index to the passage prose (see the "Performance" section of 3.3.0) now include a hash value alongside the index. If, when loading a saved game, the hash value (if present) doesn't match the code at that exact index, the entire passage is searched for a substring which matches the index's length and that hash. This should make save files slightly more resilient when the story is updated with minor edits to that passage prose.
- Additionally, Harlowe should produce better error messages when a saved game couldn't be loaded because a variable index couldn't be resolved.
Editor
- Improved syntax highlighting performance.
Debug Mode
- For performance reasons, debug replays are now limited to a maximum of 200 steps. Any further steps from 200 onwards will not be recorded.
- For performance reasons, code in the Source tab and in debug replays that is more than 9999 characters long will no longer be syntax-highlighted. (Note: passages of this length will still be highlighted in the editor.)
Additions
Editor
- Now, tab characters in passage prose are displayed as dotted gray lines. (This currently only applies to the Twine editor, not Debug Mode.)
- Added a rudimentary text completion popup menu. This is a small menu which appears when typing part of a macro name or keyword, allowing the full name to be selected and added with only a few additional keystrokes. More syntactic structures may be given text completions in the future.
Debug Mode
- Added a "Tools" panel to Debug Mode, which contains special-purpose tools to help examine the story as it is running. This currently contains the following checkbox options:
- "See through and click through (dialog:) boxes": turns dialog boxes (except debugging dialogs) 90% transparent, and lets you click through them to interact with elements (such as replay icons in Debug View) behind them.
- "Stop links, (click:) and (hover-style:) from activating": stops clicks and cursor hovers (and any other action specified by (action:)) from activating elements (debugging elements excluded). This can be useful for selecting text (or clicking on replay icons in Debug View) inside such elements.
- "Stop (go-to:), (undo:), (redirect:) and (refresh:) from activating": stops these commands from activating when they are added to the passage. They can be selectively activated later by turning on Debug View and clicking the "GO" buttons on them (next to the Replay button).
- In addition to the above, it also contains a dropdown menu which causes a multiplier to be applied to time, so as to make timed events that use it occur sooner or later. It also applies a multiplier to the delay that (after:) and (live:) wait.
3.3.3 changes (Aug 29, 2022)
Bugfixes
- Fixed an issue preventing Harlowe's editor extensions from appearing in Twine 2.5.
- Fixed a bug where a
(replace:)
call targeting the hook containing it (including special hook names like?passage
) would cause all subsequent macros and expressions to be ignored (similar to what would happen if a(go-to:)
was used).- Note: since the aforementioned behaviour is arguably more intuitive than the 3.2 behaviour, it is possible that this fix will be intentionally reverted in 4.0. As of right now, it is considered a 3.2 compatibility bug.
3.3.2 changes (Aug 28, 2022)
Bugfixes
- Fixed a bug which prevented you from naming a variable
$start
,$end
,$any
,$some
, or$all
(or naming a temp variable any of those words). - Fixed a long-standing bug where XML-style self-closing HTML tags (like
<div/>
) didn't self-close. - Fixed a crash caused by completely empty 'header', 'footer', 'startup', 'debug-header', 'debug-footer' and 'debug-startup' passages.
- Fixed a bug where, whenever a hook was shown or inserted into the passage at the same time as a dialog box (such as those created by
(dialog:)
) appeared, then existing enchantments wouldn't apply to that hook. - Debug Mode: Fixed a bug where enchantments created using
(enchant:)
with avia
lambda were displayed as(undefined:)
in the Enchantments panel.
Alterations
- Now,
<input>
elements in(prompt:)
dialogs have been given the CSS styleborder:solid white
to make them more visually consistent with(input:)
elements. - Now, Harlowe will attempt to auto-focus
(input:)
and(input-box:)
elements when they are added to the passage, allowing the player to type into them immediately. If multiple(input:)
or(input-box:)
elements are present, the first (highest) one will be auto-focused. Note that any further(input:)
elements added to the passage (via(after:)
or some other means) will be auto-focused even if the player is currently typing into an existing element.
3.3.1 changes (July 13, 2022)
Bugfixes
- Fixed a bug where the CSS for the "dissolve" transition (used for default passage transitions, among other things) wasn't compiled correctly.
- Fixed a bug where using variables containing datatypes or data patterns with the
-type
syntax (such as$pattern-type $name
) wouldn't work at all. - Now, line breaks in raw HTML
<svg>
elements are no longer converted into erroneous<br>
elements (similar to the existing exception for<table>
elements). - Fixed a bug where Debug Mode's "Turns" dropdown menu wouldn't automatically update to match the current passage.
- Toolbar: Fixed a bug where the status of the "Coding tooltips" setting wouldn't persist after closing and reopening the passage editor.
- Toolbar: Now, for toolbar dialogs that include preview panes and sliders, dragging the slider updates the pane instantly, rather than only when the slider stops moving.
Alterations
- To attempt to fit the Harlowe Toolbar onto a single row in Twine 2.4, the buttons "Heading" and "Horizontal Rule" have been moved to the List Item submenu (which is now "List and Line Items") and "Columns" has been moved to a new submenu on the "Alignment" button.
3.3.0 changes (July 5, 2022)
Bugfixes
- Fixed a long-standing bug where header passages caused aligner, column, heading, and horizontal rule markup to not work in the first line of any passage of the story.
- Fixed a long-standing bug where "incomplete" markup in headers, the main passage, or footers could "punch through" and affect anything that came after. For instance, ending a header passage with
[
and placing an unmatched]
in the main passage would erase the invisible boundary between the header and the main passage, causing things like section-based markup in the header (like aligners or columns) to affect the main passage, even though it shouldn't.- Note: for compatibility reasons, I have decided not to fix this same bug when unclosed hooks
[=
are used. So, unclosed hooks in headers, the main passage, or footers will still "punch through" to affect things that appear after. You may use this to re-create this bug's effects in 3.3.0. However, this will be fixed in 4.0.0.
- Note: for compatibility reasons, I have decided not to fix this same bug when unclosed hooks
- Fixed a long-standing bug where hook transitions would often result in only the
<tw-hook>
element's contents being transitioned, not the element itself - and as a result, the hook's contents would be wrapped in a temporary element withdisplay:block
during the transition, causing a page judder when the transition completed and the element was removed. - Fixed a long-standing bug where it was possible to put Harlowe in an infinite loop using
(save-game:)
and(load-game:)
unconditionally in the same passage. - Fixed a long-standing bug where temp variables created outside a live hook (a hook with
(live:)
,(event:)
,(more:)
, or(after:)
attached) couldn't then be used inside it. - Most of the other changers, including
(t8n:)
, should now work correctly when combined with(live:)
,(event:)
,(more:)
, or(after:)
. - Fixed a long-standing bug where the string given to
(hook:)
was case-sensitive and dash-sensitive, even though hooks named by the tag markup (<name|
) and referred to by the hook name syntax (?name
) are case-insensitive and dash-insensitive. - Fixed a long-standing bug where a Javascript error would occur if a string contained
\0
followed by two digits (except 8 or 9). - Fixed a long-standing bug where a lambda with both a
where
andvia
clause would result in thewhere
clause affecting theit
value in thevia
clause, instead of theit
value being the same in both clauses. - Fixed a long-standing bug where enchanting
?Page
with(background:)
would, if the current passage was taller than the browser window, cause the background to only cover the length of the browser window. - Fixed a bug where enchanting
?Page
with(background:)
using(gradient:)
or(stripes:)
would, upon leaving the passage with that enchantment, cause the page background to become transparent (i.e. whatever the browser's default is) instead of the default black (unless the story stylesheet changed it). - Fixed a custom macro bug where any commands inside an
(output:)
-attached hook (such as(output:)[(set:$a to it + 1)]
) would be run twice while being outputted. (Generally, you'd want to place those commands before the(output:)
hook, though.) - Fixed a bug where using spread
...
to spread the individual characters of a string that contains astral plane Unicode characters (such as 𝐇) wouldn't work at all. - Fixed a bug where the
command
datatype didn't work at all. - Fixed a bug where commands saved in variables could become mutated by being used in passage text with changers attached to them (such as a
(print:)
command in$a
being run once with(text-color:red)$a
, and then subsequent uses of$a
also being red). - Fixed a bug where
(output:)
-attached hooks nested inside multiple hooks (such as(macro: str-type _s,[ [(output:)[_s]]])
) wouldn't have access to temp variables available outside of them (such as_s
in that same example). - Fixed a bug where the
(unpack:)
macro wouldn't work at all unless the destination variables were expressed as typed variables (such asnum-type $a
). - Fixed a bug where
(folded:)
couldn't be provided with a lambda which hadwhere
,via
andmaking
clauses, where themaking
clause was between thewhere
andvia
clauses. - Now,
(rerun:?passage)
will, as expected, re-run the entire passage's code instead of simply erasing it. each
lambdas are now called "each…" lambdas (instead of just "where…" lambdas) in the Debug Mode panel.- Fixed a bug where using
(append:?Link)
(or one of its relatives) to append text to a hook enchanted with(click:)
wouldn't work correctly (the text would be placed next to the link instead of inside). - Fixed a bug where
(enchant:)
,(click:)
, and other enchantment macros could enchant empty hooks such as|A>[]
(wrapping them with<tw-enchantment>
elements), even though Harlowe usually considers empty hooks to be nonexistent, and hides them with its default CSS.- Note: this means that, given constructions like
[]<A|
and(click:?A)[]
, revision macros like(append:?Link)
will no longer consider ?A (as long as it is empty) to be a link via the(click:)
, and cannot append to it - you'll have to explicitly refer to it via(append:?A)
instead.
- Note: this means that, given constructions like
- Fixed a bug where
pos
used in lambdas given to(char-style:)
would, for each character, be a number relative to the entire passage, not the attached hook. - Fixed a bug where
(t8n-delay:)
would sometimes cause transitioning-in hooks to briefly flicker into visibility once the delay ran out and the transition began. - Partially fixed a bug where, whenever a hook or passage would finish transitioning in, elements inside it that were still transitioning would have their transitions stutter or judder.
- Note: due to inconsistencies in CSS animation performance across devices and when a large numbers of elements are simultaneously animating (in particular when using
(char-style:)
), this bug may still sometimes occur. A more robust fix for this is likely to appear in 4.0.0.
- Note: due to inconsistencies in CSS animation performance across devices and when a large numbers of elements are simultaneously animating (in particular when using
- Fixed a bug where chained
's
syntax, like$array's 1st's 2nd
, produced bad error messages when the deepest dataname (in that example,1st
) wasn't present. - Fixed a bug where
's
andof
sometimes wouldn't be syntax-highlighted correctly. - Improved the behaviour of enchanting a hook's lines (using either (enchant:) or (line-style:)) when those lines contain macros or links, sometimes causing those elements to be excluded from the enchantment, or falsely considered to be lines on their own.
- Fixed a bug where
(replace:)
targeting?passage's lines
(and similar such code) wouldn't replace the line that itself was on, unless some text was before it in the line. - The
it
identifier is now cleared (to the default value of 0) whenever the player changes passages. - Fixed a bug where metadata macros (
(storylet:)
,(metadata:)
and the like) wouldn't work at all if their macro names weren't entirely in lowercase with no dashes or underscores (which is not the case for other macros). - Fixed a bug where using a custom macro in a
(storylet:)
lambda would cause Debug Mode to constantly reload the Storylets and Variables panels, hurting performance. - Fixed a bug where single errors would sometimes be listed multiple times in Debug Mode's error log.
(mouseover:)
and(mouseout:)
enchanted elements (including those enchanted using variants of those macros) now have a tabindex attribute as well.- Fixed a bug where scrollable elements in a passage would have their scroll position reset to the top left whenever the passage's or element's transition finished.
- Fixed a bug where
(lch:)
and all lch-related colour conversions were slightly wrong, due to the conversion algorithm assuming linear-light sRGB instead of gamut sRGB. - Fixed a bug where colours created via
(lch:)
could sometimes haver
,g
orb
datavalues higher than 255 or lower than 0. - Fixed a bug where
(background:)
didn't work when given a Harlowe colour with fractionalr
,g
, orb
datavalues. - Checkboxes created by
(checkbox:)
and related macros are better vertically aligned to their text labels. (icon-counter:)
no longer uses the pointing hand cursor when the mouse hovers over it.(meter:)
now produces an error if given a bound variable using2bind
instead ofbind
(for consistency with(dialog:)
).(checkbox:)
,(input-box:)
and(force-input-box:)
now set the bound variable as soon as the element appears in the passage (For consistency with(cycling-link:)
).- Fixed a bug where Debug Mode's Variables panel listings of data structures' contents would have the wrong names (
$A's 1st
becoming just1st
) after changing passages.
Alterations
Performance
- The low-level macro-running code has been heavily rewritten. Formerly, macro code was internally converted to a syntax tree during passage rendering, then compiled into a string of Javascript, which was then executed using the browser's
eval()
function. Now, the macro syntax tree is simply interpreted and executed as-is, without the intermediary step of assembling a string. This provides performance improvements to both macro and lambda execution, which is important for frequently-called lambda-using macros like(event:)
and(storylet:)
. (However, see "Compatibility" below for a caveat regarding this change.) - Improved performance of game saving after a very large number of turns have elapsed. This affects both
(save-game:)
and the automatic game state SessionStorage saving feature (introduced in 3.0.0). - Formerly, all global variables' contents were saved in game save files as pure Harlowe source code representation. Now, if a value in a variable is considered "pure", it will be saved as an index to the passage prose in which it was originally
(set:)
or(put:)
, and will be reconstructed from that index when the game is loaded. This should save major localStorage space when a story has loads of stored strings and custom macros.- A variable is considered "pure" if its value was changed using a
(set:)
or(put:)
that did not call any of the following macros or features:(prompt:)
,(confirm:)
,(current-time:)
,(current-date:)
,(monthday:)
,(weekday:)
,(history:)
,(passage:)
,(visited:)
,it
,time
,visits
,exits
, or any other variable (except typed variable definitions, such as those given to(macro:)
). - Note that code hooks can use these inside them and still have the entire hook be considered "pure", as a code hook is comparable to a string, and nothing inside it is called until it is used.
- Also, number and boolean variables are short enough that saving them as a reference isn't necessary, so they won't be saved as indexes even if they are "pure".
- Variables changed using
(unpack:)
or2bind
currently aren't considered "pure". - As a consequence of this change, save files from older versions of your story are much more likely to become invalidated whenever you make minor changes to passage prose, so do take note of that.
- A variable is considered "pure" if its value was changed using a
- As an additional save file optimisation, global variables holding arrays, datasets or datamaps, whose inner data values have only changed slightly compared to previous turns, will now be saved in a shorter form using the
it
identifier to refer to values which were unchanged.- This optimisation also applies to global variables holding long strings, as long as they have simply been appended or prepended to over multiple turns.
- Slightly improved performance of changing passages in stories that contain a very large number of passages.
- Slightly improved performance of rendering and re-rendering hooks.
- Now, the most recent 16 visited passages (as well as all "header", "footer", "debug-header" and "debug-footer" tagged passages), have their source code's syntax trees cached, to save on rendering time if they're visited (or shown by
(display:)
) again within 16 turns. (live:)
,(event:)
and(after:)
macros now internally use requestAnimationFrame() instead of setTimeout().
Error Messages
- As an added bonus from rewriting the macro-running code (see above), the "Javascript error message" (an error caused by the Javascript engine as a result of ill-formed Harlowe code) has been completely removed, and for all Harlowe code, a proper Harlowe error message will appear in all cases from now on. (If a Javascript error message does appear, please file a bug report for each case.) This eliminates various highly misleading error messages, such as
missing ] after element list
, and replaces them with something more understandable. - Various other error messages have had their wording altered to be a bit more specific.
Coding
- Now, macros and variables in "changer position" (directly in front of a hook with only whitespace between) will no longer cause an error if they do not produce changers, booleans or commands. Instead, these values will be printed directly, as usual. This restriction was originally in Harlowe in order to protect against typos of variable names or macro names when intending to insert a changer. However, I've since decided that other error-prevention measures in the editor/syntax highlighter/Debug Mode ought to be used instead, given that this restriction tended to force authors to tediously wrap various expressions in
(print:)
calls to avoid them becoming attached. - Code hooks can now be stored in variables and printed in the passage. This means that instead of storing long strings containing large amounts of markup that doesn't get highlighted in the syntax highlighter, you can instead store code hooks. Using a code hook in this way also signals (to anyone reading the code) what its purpose is for (to be displayed in the passage).
(dialog:)
,(confirm:)
,(prompt:)
,(replace-with:)
,(append-with:)
and(prepend-with:)
have been altered to permit code hooks in place of the message string value.- Also, code hooks can be converted into strings using
(str:)
. true
andfalse
are now case-insensitive. This fixes an inconsistency between the syntax highlighter (which until now showed different-cased keywords likeTRUE
andFALSE
as valid) as well as fixes an inconsistency with other keywords, such as datatypes (which were already case-insensitive, such asINT
).(shuffled:)
and(sorted:)
now accept 1 or 0 values without causing an error. This is to make it easier to sort or shuffle arrays (by spreading...
them into these macros) without needing to care how many items the arrays contain.(data-names:)
,(data-values:)
and(data-entries:)
have been renamed to(dm-names:)
,(dm-values:)
and(dm-entries:)
, to better convey that these operate exclusively on datamaps. The original names remain as aliases.(altered:)
now accepts a lambda with awhere
clause in addition to avia
clause. If thewhere
clause produces false, the value for that loop is not altered.(sorted:)
may now accept any kind of data (not just strings and numbers) and may now be given an optional 'via' lambda, which is used to translate the values into strings or numbers, whereupon they are sorted as if they were those translated values. This can be used to sort values in a broad variety of ways.(sorted: via its name, ...$creatures)
sorts the datamaps in the array stored in $creatures by their "name" values, for instance.- The sizing strings for
(input-box:)
and(force-input-box:)
are now optional. Leaving them out means that the box occupies the entire available width. (button:)
may now be given a sizing string, identical to the one given to(box:)
and related macros, which is used to specify the button's width and horizontal margins.- You may now give one or zero values after the rotation number to
(rotated:)
- for instance,(rotated: 2, ...$arr)
will no longer make an error if $arr is empty.- Similarly, you may now give one value to
(rotated-to:)
after the lambda, instead of a minimum of two - however, if the given lambda doesn't match it, then an error will still result.
- Similarly, you may now give one value to
- Instead of producing an error,
(undo:)
and(link-undo:)
now take an optional second string, similar to(link-storylet:)
, which is shown instead of the link if undos aren't available. (source:)
,(v6m-source:)
and Debug Mode now represent built-in Harlowe colours (yellow
,blue
etc.) as their keywords, rather than a(hsl:)
macro call.- It is now an error to give two or more typed variables with the same name inside a
(p:)
string pattern (used as an(unpack:)
destination or in another such macro). - It is now an error to give
?page
to(show:)
,(hide:)
, or(rerun:)
. (mouseover:)
,(mouseout:)
and all other macros beginning with "mouse" are now deprecated. I've decided that having so many variations of (click:) and its relatives, differing only by interaction type, is a bit untidy and unnecessary. I've now created an(action:)
macro (see below) which can given as a changer to the appropriate "click" macro to replicate these macros' effects.- The
any
data name (available on arrays asany of (a:1,2)
and strings asany of "ab"
) has been renamed tosome
, to avoid confusion with theany
datatype and for naming consistency with(some-pass:)
.any
remains as a deprecated alias for compatibility, but is likely to be removed in a future version. - Custom commands (created with custom macros that use
(output:)
) can now be given to(source:)
and(v6m-source:)
. - Saving custom commands to story-wide variables no longer prevents
(save-game:)
from working - these variables can now be successfully saved in browser storage along with all the others. This change means that every valid Harlowe value (that can be set in a variable) can now be saved using(save-game:)
. - Now, when
Compatibility
- A minor (3.x) version increase usually shouldn't have significant incompatibilities with existing code, but there is something I must mention: arbitrary Javascript syntax embedded in Harlowe macro calls is no longer permitted, and will produce an error. This includes stuff like
(if: (document.title = "Wowie") is 1)[]
(which does nothing except change the window title to "Wowie") or writing(set: $a = 1)
instead of(set: $a to 1)
. This was a necessary sacrifice as a result of the aforementioned macro code rewrite. Since these were never part of the Harlowe language description, and were essentially undefined behaviour, I feel that it's fine to remove it in a "minor" version - however, this could inconvenience numerous people, which is why I'd avoided implementing this for so long. If you personally had been using this "feature" for purposes that Harlowe's macros can't fulfill, please post a bug report describing it, and I'll see what I can do about adding a macro or some other feature to support it.- Furthermore, as a result of the implementation of both this and
(seed:)
(see below), random macros will no longer produce different "rolls" when the player uses Undo to return to a previous turn - instead, the exact same roll that occurred on that turn will happen again. While this may be welcomed as a desired feature (in that it makes the Undo feature more intuitive in its behaviour), it may impact certain niche uses of these macros, so do take care.
- Furthermore, as a result of the implementation of both this and
- Due to the change to
<script>
elements inside passages (below), the run time of<script>
elements has been changed to line up with Harlowe macros and hooks. A<script>
element's code will now run as soon as it appears in the passage, instead of waiting until after every other macro and expression has run (as it did in 3.2.0 and below). Because the run time of<script>
elements was also never part of the Harlowe language description, I feel fine altering this as well, but if you were relying on that specific run time, consider wrapping your code in asetTimeout
callback or a$()
callback.
Debug Mode
- Debug Mode's colour scheme is now white-on-black, to match Harlowe's default colour scheme. You may use the new settings panel (see below) to change it back to white.
- Very long lines in "(source:)" listings in the Variables panel should no longer push the other columns far offscreen.
- Debug Mode no longer automatically, immediately enables itself whenever the first error of your story appears. Instead, this functionality can be added using the new
(after-error:)
and(debug:)
macros (see below). - Data structure values more than 4 levels deep no longer get separate rows in Debug Mode's Variables panel.
Syntax Highlighter
- The green line marking whitespace before hooks that's removed by changer attachment (added in 3.2.0) has been removed, due to the possibility of false positives when using non-changer variables in position (which is no longer an error, as mentioned above).
- Code inside HTML comments is no longer highlighted.
- Strings that match the name of an existing passage (such as those given to
(display:)
or compared with(history:)
) are now coloured differently and have an additional thin underline. Additionally, strings that match an existing passage tag now have a dotted underline. - Tooltips (if enabled) now dismiss themselves if you move the mouse cursor a bit.
Additions
Markup
- HTML
<script>
elements inside passages can now access Harlowe variables and temp variables - simply use the name of the variable as if it was a Javascript variable, such as by(set: _foo to 9)<script>_foo += 4;</script>
. However, only variables containing certain data types can be accessed in<script>
elements - numbers, booleans, strings, datamaps, datasets and arrays. Moreover, the latter three data structures can't contain any data types other than those aforementioned. Attempting to assign Javascript values to Harlowe variables that don't match any of those data types will cause an error. Note that this change only applies to<script>
elements without a "src" attribute - Harlowe variables are still inaccessible to included Javascript files. As always, consult the documentation for more details.
Coding & Macros
- Added
(str-replaced:)
(also known as(replaced:)
, although that name may cause confusion with(replace:)
if you don't understand Harlowe's convention of nouns/present tense for changers and adjectives/past tense for data macros), which allows you to find and replace sub-strings in a given string. This can take simple strings as the search and replacement patterns, but can also take(p:)
patterns and string datatypes as the search pattern, as well as "via" lambdas as the replacement.(str-replaced: (p: (p-many:digit)-type _a, "%"), via _a + " percent", "5% 10%")
produces"5 percent 10 percent"
. Also, an optional non-negative whole number can be given as the first value, to limit how many replacements are made. - Added
(p-before:)
and(p-not-before:)
, string pattern macros which can be used to add a further restriction to the previous match (inside a(p:)
or other such macro).(p-before:)
matches an empty string (that is, zero characters at that position), only if the given string pattern appears after that position.(p-not-before:)
matches an empty string unless the given string pattern appears after that position. These are best used with(str-find:)
,(str-replaced:)
and(trimmed:)
. - Added
(p-start:)
and(p-end:)
, string pattern macros which only match if their given values match the start or end of a string. These are also best used with(str-find:)
,(str-replaced:)
and(trimmed:)
. Note that thematches
operator always compares the entire string with the entire pattern, so these are identical to(p:)
when used with that operator. - Added
(mix:)
a macro which mixes two colours in the given proportions, using a different algorithm to that used when the+
operator mixes two colours. This algorithm, which is based on the LCH colourspace used by(lch:)
, compensates for differences in alpha between the two colours and generally preserves chromaticity much better. - Added
(visited:)
, which, when given a passage name or a 'where' lambda (similar to(history:)
), returns true if a matching passage has ever been visited and false if it hasn't. I suppose you're wondering why a macro for such a common idiom has only been introduced now instead of in 1.x. The answer is that prior to 3.2.0, I kept a firm guideline (barring very old exceptions like(put:)
) to not needlessly introduce highly specific macros whose functionality could be replicated with a relatively short idiomatic use of a more general macro (this is also why(live:)
was the only "live" macro until 3.0.0.) However, I've now grown to regret this direction, and appreciate that even "relatively short" idioms have room for improvement (which is why(after:)
was added in 3.2.0). In that spirit, this macro is now available. - Added
(action:)
, a macro that, like(button:)
, is only attached to links or given to(link:)
or(click:)
macros.(action:'mouseover')
changes the link(s) so that they appear and function identically to(mouseover:)
areas, and the same with(action:'mouseout')
and(mouseout:)
. This finally allows every other link macro to access the mouseover and mouseout interaction styles. There is also a new(action:'doubleclick')
interaction style available. - Added
(click-rerun:)
, a variant of(click:)
which lets the enchanted element be clicked multiple times to re-run the attached hook. This does not have a mouseover or mouseout equivalent. - Added an alias for
(link-reveal:)
,(link-append:)
, which exists mainly to clarify the relationship between it and(link:)
- while(link:)
replaces the link with the attached hook,(link-append:)
appends the hook to the link's text. (A lot of macro aliases mainly exist to aid in learning, and you are under no obligation to use them.) - Added
(input:)
and(force-input:)
, variations of(input-box:)
and(force-input-box:)
which produce single-line<input>
elements (which do not allow newlines to be typed) instead of<textarea>
elements. These do not take a height number, but are otherwise identical in usage. - Added
(digit-format:)
, a macro which converts a number to a string using a special customised format that allows for thousands separators and mandatory zeros. - Added
(unique:)
, a macro which produces an array containing the unique values in the given sequence, while preserving the order of the values.(unique:7,6,6,5,4,5)
produces(a:7,6,5,4)
. Formerly, you could produce the unique values of an array using(a: ...(ds: ...$arr))
, but this wouldn't preserve the order of values in the array. - Added
(dm-altered:)
(alias(datamap-altered:)
), a macro which serves as a combination of(altered:)
and(dm-entries:)
, while also returning an altered datamap instead of an array.(dm-altered: via its value + 1 where its name is not 'A', (dm:'A',1,'B',1,'C',1))
produces the datamap(dm:'A',1,'B',2,'C',2)
. - Added
(partial:)
, a macro which produces a custom macro that, when called, calls a given other macro (either another custom macro, or the string name of a built-in macro) with certain pre-filled values, followed by the other given values. Think of(partial:)
as creating a partial macro call - one that isn't finished, but which can be used to make finished calls to that macro, by providing the remaining values.(set: $nextLink to (partial:"link-goto","==>"))
sets$nextLink
to a custom macro which calls(link-goto:)
with "==>" as the first value.($nextLink:"Laundry")
would thus be the same as(link-goto:"==>","Laundry")
. - Added
(seed:)
, a command macro used to seed the pseudo-random number generator used for(random:)
,(either:)
,(shuffled:)
,'s random
, andrandom of
. Pass a string to it, such as by(seed:"AAA")
, and random macros and features from then on will be predetermined based on that seed. The seed is also automatically saved using(save-game:)
and loaded using(load-game:)
. This finally brings Harlowe closer into line with the other built-in formats regarding random number generator seeding. - Added
(scroll:)
, a command macro which can change the scroll position of the given hook (assuming it is scrollable, such as ?page or a (box:)-attached hook) by either a given fraction of its internal height, or to make another hook inside them become visible. - Added
(redirect:)
, a variant of(go-to:)
which doesn't create a new turn, and instead extends the current turn, but still performing the same actions as(go-to:)
- transitioning passages out and in, removing temp variables, rendering the header and footer, adding a passage name to the(history:)
array, and so forth. This precludes a problem with(go-to:)
, where using it bare in a passage will prevent the player from using the undo feature to unwind past it, as returning to that turn will cause it to immediately create a new turn. Because this is intended for actions which don't represent narrative time advancing, there isn't any plan to add link and click counterparts for this. - Added the
turns
keyword, which evaluates to the number of turns the player has taken. Because the addition of(redirect:)
means that(history:)'s length
is no longer a reliable means of measuring the number of turns taken, this keyword is functionally necessary in addition to being a convenient shorthand. - Added
(mock-turns:)
, a debug-only macro for artificially increasing the value thatturns
produces. - Added
(forget-undos:)
, a macro that removes turns from the "undo cache", preventing the player from undoing to that point ever again.(forget-undos:-2)
forgets all but the last two turns (including the current turn).(forget-undos:2)
erases the first two turns. This does not affect(history:)
,(visited:)
, or thevisits
andturns
keywords, which will continue to behave as if(forget-undos:)
was never used.- Using this to erase the entire undo cache will automatically cause
(link-undo:)
links to update themselves using their optional second string.
- Using this to erase the entire undo cache will automatically cause
- Added
(forget-visits:)
, a macro that causes Harlowe to "forget" passage visits before the specified turn number, affecting(history:)
,(visited:)
and thevisits
identifier.(forget-visits:-15)
causes all passage visits older than 15 turns to be forgotten. When combined with(forget-undos:)
, this lets you erase the two kinds of non-variable data that Harlowe stores as part of the game state and saves in browser localStorage. - Added
(after-error:)
, a variant of(after:)
which only displays the attached hook when an error message is displayed. You may use this (in a "header" tagged passage) to show a personalised message to the story's players, possibly advising them to report a bug. Or, you might use this to try and recover the story by redirecting to another passage. - Added
(debug:)
, a command which causes Debug Mode to activate (or reactivate if it was exited), even if the story wasn't a test build initially. - Added the
codehook
datatype. - The
(text-style:)
styles "tall" and "flat" have been added, which stretch or squash the text vertically. - The formerly (since Harlowe 1.0) undocumented use of the
-
operator with two strings, which removes all occurrences of the right string from the left, is now documented and "official". This is similar to using the-
operator with two arrays.
Debug Mode
- A new Debug Mode settings panel has been added, letting you set a few settings for Debug Mode's display and behaviour. These settings are saved in browser localStorage in a story-specific slot, so they should persist across debugging sessions for your story.
- The new Debug Mode settings panel also lets you toggle a new feature that turns the entire panel transparent when the mouse cursor isn't hovering on it, letting you see the whole page better.
- Added a "replay" feature to Debug Mode. When you use Debug View, special 🔍 icons will appear on macro calls in the passage. Clicking those will produce a dialog showing a step-by-step view of how the macro call's code was interpreted by Harlowe.
- Variable panel values have more detailed descriptions - for instance,
(a:)
is called "an empty array", and(a: "Weather the storm", "Search for shelter")
is called "an array (with the string "Weather the storm", and 1 other item)" instead of both being just "an array". - The width of the Debug Mode element (which you can adjust using the resizer added in 3.2.0) is now saved in localStorage alongside these settings, and should also persist across debugging sessions.
- Columns in Debug Mode panels can now be sorted by clicking the column headers, as is typical of many apps with columnar data. The columns will remain sorted as the data changes. The Storylet panel is also sorted (placing open storylets first) by default.
- The source code in the Source, Variables and Storylet panels is now syntax-highlighted, in a manner roughly matching its highlighting in the Twine editor.
- The Source panel now shows the header and footer passages' source code as well, in separate fold-down sections.
- Added an additional draggable area to to the top of each panel which, when clicked and dragged, lets you resize each panel vertically. (Panels can't be resized larger than their content.)
- Added a "Clear this panel" button to the Errors panel, which, when clicked, removes all of the recorded errors.
- Added a close button to the panel, which exits Debug Mode when clicked.
Other
- Added a Find/Replace panel to the editor toolbar.
- Added a HTML comments button to the editor toolbar, which wraps the selected text in HTML comments.
3.0.2 changes (Apr 17, 2019)
Bugfixes
- Fixed a startup bug that potentially caused
(dropdown:)
menus to stop affecting their bound variables for the rest of the game. - Now,
(alert:)
,(prompt:)
and(confirm:)
produce errors if they are used in a browser that doesn't support Javascript'sprompt()
,alert()
orconfirm()
functions, instead of crashing the page. - Fixed the
(str:)
macro alias added in 3.0.0 mysteriously not actually having been added.
3.0.1 changes (Apr 14, 2019)
Bugfixes
- Fixed a bug where the SessionStorage state-preserving system introduced in 3.0.0 would interfere with the "Test story starting here" feature in the Twine editor.
Alterations:
- If the
(loadgame:)
macro encounters an error while loading save data (such as, a passage it refers to no longer exists in this version of the story) then a polite dialog box (a simple JavaScriptprompt()
) will appear suggesting that the save data might be outdated, and asking the reader whether or not the save data should be deleted.
3.0.0 changes (Jan 11, 2019)
Bugfixes
- Fixed a bug where the story crashes on startup if the page's URL contains a hash identifier that doesn't have "stories" in it (such as in
story.html#what
). - Fixed a bug where the story crashes on startup if, for some reason, localStorage exists but cannot be accessed.
- Fixed a bug where same-precedence arithmetic operators (
+
and-
, or/
and*
) had incorrect associativity (so,3 - 5 + 2
was interpreted as3 - (5 + 2)
instead of(3 - 5) + 2
). - Temp variables finally work correctly with changers that defer a hook until some event occurs, like
(link:)
,(click:)
and such. Now, you can reference temp variables inside the hook, such as in(link:"Read")[It reads: _engraving]
, just as you can with other kinds of changers. - Fixed a bug where supplying multiple shortened
is
oris not
comparisons, in a form such as$a is $b and $c
, would produce an incorrect result. - Fixed a bug where the debug view's
(set:)
messages were worded incorrectly when setting global variables. - A more useful error message is given if you write a link with no passage name (such as
[[Go->]]
). - Fixed a bug where you could + a changer, a hookname, or a colour to any arbitrary command, like a
(goto:)
command. - Fixed a bug where having a
(for:)
macro's lambda'swhere
clause return something other than a boolean, such as in(for: each _a where 127)[]
, wouldn't produce an error message. - Fixed a bug where
contains
would wrongly error if used to check if an empty string""
contained anything.
Alterations
- Now, when playing, the current game session will attempt to preserve itself across browser reloads and back-forward navigation using browser SessionStorage. This means that reloading the page (without closing the window or tab) should also automatically reload the player's position in the story, as if by
(load-game:)
. This does not apply when using(reload:)
, however, which always returns the story to the beginning. If an error occurs while loading this data (such as, a passage it refers to no longer exists in this version of the story) then it will be silently ignored. - The
(replace:)
,(append:)
and(prepend:)
macros now no longer target any hooks or text that haven't been rendered yet - so,(replace: "cool")[hot] cool water
won't work because the(replace:)
runs before "cool water" has rendered, butcool water (replace: "cool")[hot]
and something like(link: "heat")[(replace: "cool")[hot]] cool water
will. This finally normalises what was formerly very inconsistent behaviour across these three macros -(replace:)
couldn't target forthcoming hooks but could target later text, and(append:)
and(prepend:)
would do the others' behaviour on forthcoming hooks. - The
(text:)
macro now has another alias,(str:)
. This alias will now be the preferred name for this macro in the documentation, mainly due to the arrival of other string macros that begin with "str-", and additionally to avoid semantic conflict with the various "text-" changer macros like(text-style:)
. - To more clearly separate the concepts of "printing data" and "running commands" in Harlowe, the
(print:)
macro will no longer run commands passed to it (that is,(print:(go-to:"Foo"))
and(go-to:"Foo")
will no longer do the same thing - the former will just print out a descriptive string, as if printing out a changer). Commands can now only be run by placing them directly in the passage (either as plain calls, inside variables, or wrapped in strings that (print:) receives). - Passage links no longer have a
passage-name
attribute indicating which passage they lead to, which the player could inspect using developer tools. - If links' text contains an error message (for instance, in the case of
(link-replace:"(print:2+true"))[]
), then the link can no longer be clicked (so that the error message can be expanded). - Error messages should now explain in slightly more detail what kind of lambda a macro requires (
"a "where ..." lambda"
, for instance). - Now, a
(transition:)
added to(link:)
s,(click:)
s,(mouseover:)
s and other such macros will no longer cause the links or other elements to use the named transition themselves - instead, it will only be applied to the attached hook when it is made to appear. (show:)
will no longer produce an error if it tries to show a hook that's already visible, for consistency with other macros that accept hooknames, like(click:)
. (Actually, it never did this in the first place, due to a bug.)- The "undo" and "redo" buttons in the story's sidebar are now brighter by default.
- Various lambda macros that accept multiple values - namely
(for:)
,(all-pass:)
,(some-pass:)
,(none-pass:)
,(find:)
, and(altered:)
- no longer error if no values are given after the lambda - for instance,(for: each _a, ...$arr)[]
now no longer errors if$arr
contains 0 elements. - Syntax highlighting: Tweaked a few colours to be more readable in dark mode, and removed the "smart quote" skewing to signify matching pairs of quote marks (as it was interfering with the cursor position sometimes).
Additions
Datatypes
- Added
is a
andis an
operators, which can be used to determine what datatype a variable or piece of data is -$message is a string
is true if the variable is a string. The datatype names arenumber
ornum
,string
orstr
,boolean
,array
,datamap
ordm
,dataset
ords
,command
,changer
, andcolor
orcolour
. - Added a
matches
operator, which functions similarly tois a
, but can also be used to check if a data structure's shape resembles a pattern - a similar data structure with datatype names as "holes" in it.(a: 2, 3) matches (a: num, num)
checks that the first array contains exactly two numbers.(dm: "Faction", str) matches (dm: "Faction", "Slugbikers")
checks that the second datamap contains only one name with a string value. Nested patterns -(a: (a: num), num, num)
- are also usable. - Added a
bind
operator, which is used to "bind" variables to certain interaction macros, like(cycling-link:)
, described below.
Macros
- You can now attach changers to command macros, including
(print:)
,(display:)
and(link-goto:)
, as well as regular passage links. This allows you to style them without needing to wrap them in a separate hook. (A subset of commands, like(enchant:)
and(stop:)
, still can't have changers attached, however.) - Added
(transition-depart:)
and(transition-arrive:)
(aliases(t8n-depart:)
and(t8n-arrive:)
), macros which, along with an optional(t8n-time:)
, allow you to finally change the passage transition used by links, by just attaching them to the front:(t8n-depart:"dissolve")[[Think it over]]
will create a link that, when clicked, goes to the "Think it over" passage and fades out the current passage using a dissolve transition. These can be used in tandem for a number of interesting effects:(t8n-depart:"dissolve")+(t8n-arrive:"pulse")[[That memory...]]
will work as expected. You can also use these with(link-goto:)
,(link-undo:)
, and work with(enchant: ?Link)
too. - The following transitions have been added:
- "instant", which makes the transitioning entity instantly appear or disappear. (Try placing
(enchant:?Link, (t8n-arrive:"instant"))
in your header passages for snappy transitions throughout the story.) - "rumble", which shakes the transitioning entity vertically as it appears or disappears (similar to
(text-style: "rumble")
). - "slide-up", "slide-down", "slide-left" and "slide-right", which slide the transitioning entity in or out from offscreen.
- "flicker", which coarsely flickers the transitioning entity as it appears or disappears, reminiscent of flickering fluorescent lights.
- "instant", which makes the transitioning entity instantly appear or disappear. (Try placing
- Added
(cycling-link:)
, a popular interaction macro from Twine 1. Clicking a cycling link changes its text to the next string in the given list, and can optionally set a variable to that same string. An example usage is(cycling-link: bind $flower, "rose", "violet", "daffodil")
. This can use an attached(t8n:)
to transition each string in as it's clicked. (I'd meant to include for a very long time, but it needed several other features in this update to fully meet the UX standards of Harlowe.) - Added
(dropdown:)
, an interaction macro similar to(cycling-link:)
that creates a dropdown<select>
menu instead, and binds a variable to whichever string is currently selected. - Added a string-specific shorthand of
(repeated:)
called(str-repeated:)
(and aliased as(string-repeated:)
).(str: ...(repeated: 14, "-+*+"))
is the same as(str-repeated: 14, "-+*+")
. - Added
(reversed:)
, a macro which constructs an array with the given elements in reverse order, and(str-reversed:)
, a shorthand that reverses a single string's characters. (Prior to now, you could accomplish this with(folded: _e making _a via (a: _e) + _a, (a:), ...$arr)
, but this offers a far easier formulation.) - Added
(click-goto:)
,(mouseover-goto:)
and(mouseout-goto:)
, which are combinations of(click:)
,(mouseover:)
and(mouseout:)
with(goto:)
, similar to(link-goto:)
. - Added
(link-reveal-goto:)
, a combination of(link-reveal:)
and(go-to:)
that lets you run commands like(set:)
before going to another passage. An example usage is(link-reveal-goto: "Link text", "Passage name")[(set: $x to 1)]
. - Added
(link-show:)
, a link which, when clicked, shows the given hidden hooks, as if by(show:)
. Just like(show:)
, it can also have changers attached to it. - Added
(event:)
, a changer similar to(live:)
which live-renders the hook only once, and only when the given lambda, which is run every 20ms, produces true. It accepts a "when" lambda, which is just a version of "where" that's grammaticaly appropriate for(event:)
.
Appendix
Harlowe 4.0 roadmap
Last updated 2024-01-01
Since 3.0.0, I've been stockpiling ideas for breaking changes to the Harlowe language, and the next major version is where I intend to put them into reality. Here is a decent sample of a few of them. Note that there is no guarantee that any of these will ever be implemented quite as described.
Virtual DOM: Instead of the current naïve DOM manipulation that makes macros like
(live:)
and the various transitions rather brittle, even a basic virtual DOM would provide serious performance and stability improvements. This is likely to be the trickiest feature to implement, but there's no shying away from it now.Eliminating enchantment wrappers from DOM structures: This ties into the Virtual DOM, above, but a number of tough visual problems in Harlowe stem from
(enchant:)
and other macros having to add and remove wrappers from various elements in order to cause various CSS effects. Replacing these with better enchantment tracking and CSS style creation is somewhat overdue.Better handling of line breaks: Past Harlowe versions have had a special case that reduces consecutive line breaks' cumulative heights, and while that has some aesthetic purpose, the resulting inconsistency has become too pesky and should really be removed. In addition to this, I want to try wrapping prose lines in DOM wrappers (not
<p>
elements per se, but something similar) and having those serve the purpose of breaking lines instead of<br>
. This feeds into the previous feature as well, making(line-style:)
more robust by eliminating the need for temporary wrappers.Something I've been calling "the syntax that should have been in 1.0": I've thought of a reform of the link syntax inspired by Ink. Harlowe has long been bothered by the fact that links and hooks use the same glyphs,
[]
. This new feature would confront the issue head-on.- Placing
->
or<-
inside any hook would transform it into a link, so you only need to type[link->passage name]
. If you remove the passage name, then it becomes a "revealing" link[link->]
that reveals the nearest hidden hook. Leaving off the link text (like[->passage name]
or[->]
) would turn it into a(click:?page)
interaction instead of a textual link. - Also, like Ink, any unnamed unattached hooks inside the link text, like
["Really[?"]->]
would be removed when it is clicked. - To make the existing link syntax
[[link text->passage name]]
and[[link]]
compatible with this new syntax,[[
and]]
may become special hook delimiters that force legacy rules onto its contents: when no link arrow is present, it is treated as if it was a link, and moreover,|
is treated as equivalent to->
(contradicting the "sequence syntax" below). Though, authors would be recommended to use the aforementioned click-page interaction[->link]
instead. All of these changes allow in-passage revealing links to be on the same footing as between-passage links, with both using the same basic syntax. This all might seem a little abstract from just this high-level description, but I have high hopes for it and the story code it can enable.
- Placing
In addition to the above, I also envision new sequence hook syntax that revives several ideas for text-revealing I'd had back in the Twine 1.4 days. The glyph
|
would split a hook into a "sequence" comprising a visible portion and several hidden portions -[visible|hidden 1|hidden 2]
. Clicking any revealing links inside a visible portion[visible [link->] portion|hidden portion]
would unlink the text, remove any unnamed unattached hooks in the visible portion, and reveal the next hidden portion.Comments that work both inside and outside macro calls: I'm considering a special "comment" glyph,
--
(or possibly a new glyph like '!'), which turns the next element into a comment, which Harlowe will dutifully ignore. Place--
in front of a hook to turn that entire hook into a "block comment", or place it in front of a macro call (both in-passage and inside a call) to ignore it altogether, without needing(ignore:)
. Additionally, place it in front of any other code element, such as a number, to "cross it out".(set: $hp to --3 2)
sets the variable to 2, ignoring the 3.Units as separate datatypes from numbers: instead of the crude
s
andms
syntax for providing seconds to(t8n-time:)
and other macros, having times be a special unit separate from numbers, and having sizing lines like"==XX=="
be separate from strings, allows for better type signatures and error messages for built-in macros and custom macros alike.Eliminating the default 0 value for story-wide variables: Not much to say, except that this has been in Harlowe since 1.0, and has had an overall negative effect on story debugging. Also, story-wide variables should really act more like temp variables instead of having a special case like this.
Nullable
's
andof
syntax: a feature that has long been on the horizon, this would let authors write something like(set: $a to $b's? 1st)
, without an error occurring if $b doesn't have a1st
value at all. The problem here has always been that it requires implementing a properNone
value (distinct fromfalse
) for Harlowe, or else adding short-circuiting operators like "guard" and "default".Dropping Internet Explorer support: It's sad to say, but supporting IE's older version of Javascript and CSS no longer seems like a worthwhile use of my development resources in the current browser landscape, so Harlowe 4.0 will likely drop support in favour of using the latest CSS as-is. Harlowe already provides features that only function outside of IE, so this shouldn't be entirely unexpected.
Localisation: Getting Harlowe up to the localisation standards of the Twine 2 user interface is also long overdue. While this will be slightly laborious (due to Harlowe's large amount of built-in message strings), it likely won't have the same infrastructural issues as other listed features (which is to say, the underlying game engine shouldn't be affected too much).
All that being said, here are a few things I will not be changing in 4.0:
1-indexing for sequences, or any changes for the
1st
andlast
data names. (Even if I thought this was a problem, which I don't, it's a smidge late to change something so fundamental.)Pass-by-value for everything, including data structures. (I remain adamant that pass-by-reference is simply not beginner-friendly.) I'll likely try to add more sophisticated copy-on-write optimisations, though, so that data structure copying is a bit more efficient.
And finally: Harlowe 4.0 currently has no release date. However, unstable builds provide a preview of what's actually made it into code (for now…)
Operators and order-of-operations
The following table is a complete list of all operators and other non-identifier keywords in Harlowe, as well as the order of operations that Harlowe uses to compute expressions. This is a rough summary of information found in the "Types of data" articles in this manual. For more details on these operators and how they interact with a given data type, please consult those articles.
These operators are listed in reverse order of operation. Those with a lower order number are evaluated first.
Order | Operator(s) | Syntax Description | Usage |
---|---|---|---|
17 | , |
Any , [Any] |
Commas are not actually operators, but part of the macro syntax, used to separate expressions given to a macro. However, to express their order-of-operations relationship to the actual operators, they are listed here. Just remember: everything between commas (inside a single macro call) is evaluated separately from each other. Also note that trailing commas (commas without a right side) in macro calls are valid, even though they don't do anything |
16 | to , into |
Variable or TypedVar to AnyAny into Variable or TypedVar |
Used only by (set:) and (put:), these put the data value into the named variable. If the variable is a TypedVar, it becomes restricted to that datatype or data pattern first (which may cause the entire operation to error). |
15 | where , when , via |
[Lambda or Variable or TypedVar] where Expression[Lambda or Variable or TypedVar] when Expression[Lambda or Variable or TypedVar] via Expression |
Produces a lambda of the given type. The entire expression to the right is not evaluated, but stored inside the lambda. If another lambda is to the left, they are both joined into one. If a Variable or TypedVar is to the left, that becomes the name of the lambda's loop variable. |
making , each |
[Lambda or Variable or TypedVar] making Variable or TypedVareach Variable or TypedVar |
Produces a lambda of the given type. The Variable or TypedVar becomes the name of the lambda's "making" variable if it's a making lambda, and otherwise it becomes the name of the lambda's loop variable. Note that making cannot be given to any macros without being combined with at least a via lambda. |
|
14 | -type |
Any -type Variable |
Used to create a TypedVar, which is a variable name combined with a datatype. |
13 | and , or |
Boolean and Boolean‡Boolean or Boolean‡ |
Used to perform the basic logic operations on Boolean values, producing true or false . |
12 | is , is not |
Any is Any†Any is not Any† |
Produces Boolean true if the value on the left is exactly identical to the value on the right, and false otherwise. |
11 | contains , does not contain , is in , is not in |
Array or Dataset contains Any†Array or Dataset does not contain Any†Any is in Array or Dataset†Any is not in Array or Dataset† |
Produces Boolean true if the value is (or is not) inside the array or dataset as a data value, and false otherwise. |
Datamap contains Any†Datamap does not contain Any†Any is in Datamap†Any is not in Datamap† |
Produces Boolean true if the value is (or is not) a data name of the datamap, and false otherwise. (To check that a datamap contains a value, try using this with (dm-values:)) |
||
String contains String†String does not contain String†String is in String†String is not in String† |
Produces Boolean true if one of the strings is (or is not) a substring of the other, and false otherwise. |
||
10 | is a , is not a |
Any is a Datatype†Any is not a Datatype† |
Produces Boolean true if the datatype describes the value, and false otherwise. |
matches , does not match |
Any matches Any†Any does not match Any† |
Produces Boolean true if one value matches the data pattern on the other, and false otherwise. |
|
9 | < , <= , >= , > |
Number < Number†Number <= Number†Number >= Number†Number > Number† |
Used to perform mathematical comparisons on numbers, producing true or false . |
8 | + , - |
Number + NumberNumber - Number |
Used to perform mathematical addition and subtraction on numbers. |
String + StringString - String |
Used to join strings, or produce a copy of the left string with all occurrences of the right string removed. | ||
Array + ArrayArray - Array |
Used to join arrays, or produce a copy of the left array with all occurrences of the right array's values removed. | ||
Dataset + DatasetDataset - Dataset |
Used to join datasets, or produce a copy of the left datasets with all occurrences of the right datasets's values removed. | ||
Datamap + Datamap |
Used to join datamaps, using the right side's value whenever both sides contain the same name. | ||
Colour + Colour |
Used to mix colours (using the sRGB mixing algorithm). | ||
Changer + Changer |
Used to combine changers. | ||
HookName + HookName |
Produces a special complex HookName that refers to both of the HookNames' hooks. | ||
7 | * , / |
Number * NumberNumber / Number |
Used to perform mathematical multiplication and division on numbers. |
6 | ... |
... Array |
Spreads out the individual elements of an array into the containing macro call, as if each element had been listed and separated with commas. |
... String |
Spreads out the individual characters of a string into the containing macro call, as if each character had been listed and separated with commas. | ||
... Dataset |
Spreads out the individual elements of a dataset into the containing macro call, in sorted order (as if by the (sorted:) macro). | ||
... Datatype |
Produces a special "spread" version of the datatype, which, when used in arrays or patterns, matches zero or more of its values. Can also be used with -type to make multi-value TypedVars for the (macro:) macro. |
||
bind , 2bind |
bind Variable2bind Variable |
Used to make Binds, which allow a variable name to be "bound" to a particular interaction macro, like (dialog:). Two-way binds force the variable to always have the currently-selected value of the interaction element. | |
5 | not |
not Boolean |
Turns false into true and vice-versa. |
+ , - |
+ Number- Number |
- is used to make positive numbers negative, and vice-versa. + is less useful, but may be used to check if a variable is a number: +$a produces an error if $a does not hold a number. |
|
4 | of |
Dataname or Number or String of Any |
Used to obtain data values from certain data types (Array, String, Datamap, Dataset, Colour, CustomMacro, Gradient, or TypedVar). |
Dataname or Number or String of HookName |
Produces a variant of the HookName which only refers to one, two, or a subrange of the hooks with its name. | ||
3 | 's , its |
Any 's Dataname or Number or Stringits Dataname or Number or String |
Used to obtain data values from certain data types (Array, String, Datamap, Dataset, Colour, CustomMacro, Gradient, TypedVar). 's cannot have spaces between it and the value to the left. its is a special combination of this operator with the it identifier. |
HookName 's Dataname or Number or StringDataname or Number or String of HookName |
Produces a variant of the HookName which only refers to one, two, or a subrange of the hooks with its name. | ||
2 | Data values (numbers, strings, macro calls, etc.) | Again, these are not operators, but are listed here to express their order-of-operations relationship to the real operators (and to the grouping operator, below). | |
1 | ( , ) |
( Any ) |
Much like in arithmetic, brackets can be used to force a certain sub-expression to be evaluated before others. |
†. When the left side is missing, Harlowe will try to infer a missing it identifier to automatically put there, instead of just producing an error.
‡. When either side is not a Boolean, Harlowe will try to infer a missing it identifier and a missing comparison operator to automatically put there, instead of just producing an error.
Previous Harlowe versions and manuals
If you need to add a previous version of Harlowe to your copy of Twine 2, then you may use these links to do so.
- Harlowe 3.3.9 (Jun 16, 2024 - last? version of 3.x)
- Harlowe 3.2.3 (Oct 22, 2021 - last version of 3.2.x)
- Harlowe 2.1.0 (Dec 07, 2017 - last version of 2.x)
- Harlowe 1.2.4 (Apr 26, 2017 - last version of 1.x)
Right-click these links to copy their URLs, then use them in Twine 2's "Add a Story Format" dialog.
And, if you need to access the manuals for past versions, here they are.
Use of past versions of Harlowe is not recommended unless you are, for instance, attempting to edit a story already made in those versions.
This manual was generated at: Mon Jan 01 2024 17:31:06 GMT+1000 (Australian Eastern Standard Time)