本文档是 SugarCube v2 的中文参考手册,这是一个为 Twine/Twee 设计的免费且自由的故事格式。
提示: 本文档为单页结构,您可使用浏览器的页面内搜索功能——CTRL+F、CMD+F、F3——快速定位关键词。
注意:
若您发现 SugarCube 的漏洞或有改进建议,请前往其 源代码仓库 提交新议题。
若您发现汉化有问题或有更好的建议,请前往汉化文档仓库
文档贡献者(排名不分先后):
简体中文翻译贡献者(排名不分先后):
注意:
除非特别说明,所有标记语法自 v2.0.0
版本起可用。
除了使用打印宏(<<print>>
、<<=>>
、<<->>
)来输出变量值,SugarCube 的裸变量标记允许直接在段落文本中插入变量——即段落文本中的变量会被自动替换为其值的字符串表示。
裸变量标记支持以下形式:
类型 | 语法 | 示例 |
---|---|---|
简单变量 |
|
|
属性访问 (点号表示法) |
|
|
索引/属性访问 (方括号表示法) |
|
|
如需进行更复杂的操作(例如使用计算表达式:$variable[_i + 1]
,或方法调用:$variable.someMethod()
),仍需使用打印宏。
示例:
/* 使用 <<print>> 宏显式打印 $name 的值 */
你好啊,<<print $name>>。
/* 使用裸变量标记隐式打印 $name 的值 */
你好啊,$name。
/* 假设 $name 的值为 "Mr. Freeman",两种方式都会输出: */
你好啊,Mr. Freeman。
由于段落文本中的变量会自动转换为它们的值,如果您需要按原样输出变量(不进行插值,例如用于教程、调试输出等用途),则需要通过某种方式对其进行转义。例如:
/* 使用 nowiki 标记:"""..."""(三重双引号) */
变量 """$name""" 的值为:$name
/* 使用 nowiki 标记:<nowiki>...</nowiki> */
变量 <nowiki>$name</nowiki> 的值为:$name
/* 使用双美元符号标记(转义$符号):$$ */
变量 $$name 的值为:$name
/* 假设 $name 的值为 "Mr. Freeman",所有示例将输出: */
变量 $name 的值为:Mr. Freeman
此外,您可以使用内联代码标记来转义变量,但这样做会将转义的变量包裹在 <code>
元素中,因此可能最适合示例和教程使用。例如:
/* 使用内联代码标记:{{{...}}}(三重花括号) */
变量 {{{$name}}} 的值为:$name
/* 假设 $name 的值为 "Mr. Freeman",将输出: */
变量 <code>$name</code> 的值为:Mr. Freeman
SugarCube 的链接标记由必需的 Link
组件和可选的 Text
、Setter
组件组成。
Link
组件可以是纯文本或任何有效的 TwineScript 表达式(会在链接初始化时解析),其值应为段落名称或有效 URL(本地或远程)。
Text
组件(可选)可以是纯文本或任何有效的 TwineScript 表达式(会在链接初始化时解析)。
Setter
组件(仅适用于段落链接,可选)必须是有效的 TwineScript 表达式,格式与 <<set>>
宏相同(会在点击链接时解析)。如需多个表达式,请用分号分隔(;
)——例如:$a to 5; $b to true
。
除标准管道符 (|
) 分隔外,SugarCube 还支持箭头分隔符 (->
& <-
)。箭头方向决定组件顺序,箭头始终指向 Link
组件——右箭头为 Text->Link
,左箭头为 Link<-Text
。
警告 (Twine 2):
由于 Twine 2 的自动段落创建机制,使用表达式作为 Link
组件会生成以表达式命名的冗余段落。建议在 Twine 2 中使用 <<link>>
宏的独立参数形式来避免此问题。
语法 | 示例 |
---|---|
|
|
|
|
|
|
|
|
SugarCube 的图片标记由必需的 Image
组件和可选的 Text
、Link
、Setter
组件组成。
Image
组件可以是纯文本或任何有效的 TwineScript 表达式(会在图片初始化时解析),其值应为图片资源的有效 URL(本地或远程)或媒体(图片)段落名称。
Text
组件(可选)可以是纯文本或任何有效的 TwineScript 表达式(会在图片初始化时解析),其值将作为图片的替代文本(alt 文本)。
Link
组件(可选)可以是纯文本或任何有效的 TwineScript 表达式(会在图片初始化时解析),其值应为段落名称或有效 URL(本地或远程)。
Setter
组件(仅适用于段落链接,可选)必须是有效的 TwineScript 表达式,格式与 <<set>>
宏相同(会在点击链接时解析)。如需多个表达式,请用分号分隔(;
)——例如:$a to 5; $b to true
。
除标准管道符 (|
) 分隔外,SugarCube 还支持箭头分隔符 (->
& <-
)。箭头方向决定组件顺序,箭头始终指向 Image
组件——右箭头为 Text->Image
,左箭头为 Image<-Text
。
警告 (Twine 2):
由于 Twine 2 的自动段落创建机制,使用表达式作为 Link
组件会生成以表达式命名的冗余段落。建议在 Twine 2 中使用 <<link>>
宏的独立参数形式来避免此问题。
语法 | 示例 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
样式表中可使用图片标记的受限子集,仅允许使用 Image
组件——主要是便于使用媒体(图片)段落。例如:
/* 使用外部图片 "forest.png" 作为 <body> 背景 */
body {
background-image: [img[forest.png]];
}
/* 使用媒体段落 "lagoon" 作为 <body> 背景 */
body {
background-image: [img[lagoon]];
}
注意: 以下功能在原始 HTML 标记中均不可用。
SugarCube 提供了一些特殊的 HTML 和 SVG 属性,您可以将它们添加到标签中以启用特殊行为。这些属性用于段落链接、媒体段落和设置器。
类型 | 属性 | 示例 |
---|---|---|
段落链接 |
|
|
音频段落 |
|
|
图片段落 |
|
|
资源段落 |
|
|
视频段落 |
|
|
设置器 |
|
|
v2.0.0
:引入该功能v2.24.0
:新增对 <audio>
, <source>
, <video>
标签的 data-passage
属性支持HTML和SVG 属性可通过添加指令前缀(特殊文本)来触发特殊处理。
sc-eval:
, @
此指令会将属性值作为 TwineScript 表达式进行求值。处理后,指令前缀将从属性名中移除,求值结果将作为属性实际值。
警告:
data-setter
属性禁止使用求值指令(因其功能是在元素激活时求值内容),尝试使用将导致错误。
语法 | 示例 | 渲染结果 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
v2.21.0
: 引入该功能v2.23.5
: 修复了单个 HTML 标签使用多个指令时部分未处理的问题相关参考:
各类无间断功能——<<nobr>>
宏、nobr
特殊标签和Config.passages.nobr
设置——均提供类似但略有差异的功能。
注意: 行继续标记(或任何依赖行定位的标记)与无间断功能不兼容,因其工作原理存在冲突。
行首或行尾的反斜杠(\
)即行继续标记。处理时会移除反斜杠、关联的换行符及两者间的所有空格——从而实现多行内容的无缝拼接。此功能主要用于需要换行排版提升可读性,但又不想在显示时产生额外空格的场景,当需要输出内容时可用此替代无法使用的<<silently>>
宏。
例如以下写法(注:·
表示将被移除的空格,¬
表示换行符):
西班牙的降水 \¬
主要集中在平原.
西班牙的降水 \····¬
主要集中在平原.
西班牙的降水¬
\ 主要集中在平原.
西班牙的降水¬
····\ 主要集中在平原.
在最终输出中将会输出此行:
西班牙的降水主要集中在平原.
行首的感叹号(!
)用于定义标题标记。1 到 6 个感叹号分别对应不同层级的标题,数量越多层级越低。
类型 | 语法与示例 | 渲染结果 | 显示效果(大致) |
---|---|---|---|
一级标题 |
|
|
一级标题 |
二级标题 |
|
|
二级标题 |
三级标题 |
|
|
三级标题 |
四级标题 |
|
|
四级标题 |
五级标题 |
|
|
五级标题 |
六级标题 |
|
|
六级标题 |
注意: 由于样式标记使用相同符号作为起止符,同类型样式不能嵌套使用。
类型 | 语法与示例 | 渲染结果 | 显示效果(大致) |
---|---|---|---|
强调 |
|
|
强调 |
加粗 |
|
|
加粗 |
下划线 |
|
|
下划线 |
删除线 |
|
|
|
上标 |
|
|
文字上标 |
下标 |
|
|
文字下标 |
行首的星号(*
)或井号(#
)分别用于定义无序列表和有序列表的成员项。
类型 | 语法与示例 | 渲染结果 | 显示效果(大致) |
---|---|---|---|
无序列表 |
|
|
|
有序列表 |
|
|
|
行首的右尖括号(>
)用于定义块引用标记。多个右尖括号表示嵌套的块引用层级。
语法与示例 | 渲染结果 | 显示效果(大致) |
---|---|---|
|
|
第一行 |
Type | Syntax & Example | Rendered As | Displays As (roughly) |
---|---|---|---|
Inline |
|
|
|
Block |
|
|
|
A set of four, or more, hyphen-minus (-
) characters that begin a line, and are the only things on the line, define the horizontal rule markup.
Type | Syntax & Example | Rendered As | Displays As (roughly) |
---|---|---|---|
Horizontal rule |
|
|
The verbatim text markup disables processing of all markup contained within—both SugarCube and HTML—passing its contents directly into the output as plain text.
Type | Syntax & Example | Rendered As | Displays As (roughly) |
---|---|---|---|
Triple double quotes |
|
|
No //format// |
<nowiki> tag |
|
|
No //format// |
A set of opening and closing <html> tags—i.e., <html></html>
—defines the verbatim HTML markup. The verbatim HTML markup disables processing of all markup contained within—both SugarCube and HTML—passing its contents directly into the output as HTML markup for the browser. Thus, you should only use plain HTML markup within the verbatim markup—meaning using none of SugarCube's special HTML attributes or directives.
Note: You should virtually never need to use the verbatim HTML markup.
Warning: Because the custom style markup uses the same tokens to begin and end the markup, it cannot be nested within itself.
Type | Syntax | Example | Rendered As |
---|---|---|---|
Inline |
|
|
|
|
|
||
Block |
|
|
|
|
|
;
) separated list consisting of one or more of the following:
#alfa
..bravo
.color:red
.v2.31.0
, the ID and class names components may be conjoined without need of extra semi-colons—e.g., #alfa;.bravo;.charlie;
may also be written as #alfa.bravo.charlie;
.
A text replacement markup. The template markup begins with a question mark (?
) followed by the template name—e.g., ?yolo
—and are set up as functions-that-return-strings, strings, or arrays of either—from which a random member is selected whenever the template is processed. They are defined via the Template
API.
For example, consider the following markup:
?He was always willing to lend ?his ear to anyone.
Assuming that ?He
resolves to She
and ?his
to her
, then that will produce the following output:
She was always willing to lend her ear to anyone.
v2.29.0
: Introduced.Note: Comments used within passage markup are not rendered into the page output.
Type | Syntax & Example | Supported Within… |
---|---|---|
C-style, Block |
|
Passage markup, JavaScript, Stylesheets |
TiddlyWiki, Block |
|
Passage markup |
HTML, Block |
|
Passage markup |
TwineScript in SugarCube is, essentially, JavaScript with an extra spoonful of sugar on top to make it a bit nicer for the uninitiated.
Note:
Temporary variables were added in v2.3.0
.
A variable is a bit of storage where you may stash a value for later use. In SugarCube, they come in two types: story variables and temporary variables. Story variables are a part of the story history and exist for the lifetime of a playthrough session. Temporary variables do not become part of the story history and only exist for the lifetime of the moment/turn that they're created in. You'll likely use story variables most often throughout your project—though, temporary variables are perfect candidates for things like loop variables, if you're using the <<for>>
macro.
For example, you might use the story variable $name
to store the main player character's name or the story variable $cash
to store how much money the player has on hand.
Values may be of most primitive types and some object types, see Supported Types for more information.
There are two other variables where you may store values:
setup
object provided by SugarCube. Its properties may be used for storing non-persistent values—i.e., values that are not persisted through navigation or stored in saves.window
object provided by the browser. Its properties may also be used for storing non-persistent values (see above), however, care must be taken not to overwrite any of its many predefined properties. Another feature is that any property defined on it becomes an auto-global—e.g., window.doTheThing
may be accessed as doTheThing
regardless of scope.The names of both story and temporary variables have a certain format that they must follow—which signifies that they are variables and not some other kind of data.
The very first, and mandatory, character is their sigil, which denotes whether they are a story or temporary variable. The sigil must be a dollar sign ($
) for story variables or an underscore (_
) for temporary variables.
The second, and also mandatory, character of the variable name may be one of the following: the letters A though Z (in upper or lower case), the dollar sign, and the underscore (i.e., A-Za-z$_
)—after their initial use as the sigil, the dollar sign and underscore become regular variable characters.
Subsequent, optional, characters have the same set as the second with the addition of numerals (i.e., 0-9
, so the full set is A-Za-z0-9$_
). No other characters are allowed.
A few examples of valid names:
/* Story variables */
$cash
$hasKeyCard5
$met_alice
$TIMES_POKED_MR_BEAR
/* Temporary variables */
_i
_something2
_some_loop_value
_COUNT
Note: This is not an exhaustive list. There are many ways to use and interact with variables.
To modify the values contained within variables, see the <<set>>
macro and setter links.
To print the values contained within variables, see the naked variable markup and the <<print>>
, <<=>>
, and <<->>
macros.
To control aspects of your project based on the values contained within variables, see the <<if>>
and <<switch>>
macros.
The following types of values are natively supported by SugarCube and may be safely used within story and temporary variables.
true
& false
42
, -24
; floating-point: 3.14
, -17.76
; special: Infinity
, NaN
"I like pie"
, 'You like pie'
null
undefined
Array
Date
Map
Set
Any supported object type may itself contain any supported primitive or object type.
Unsupported object types, either native or custom, can be made compatible by implementing .clone()
and .toJSON()
methods for them—see the Non-generic object types (classes) guide for more information.
Due to how SugarCube stores the state history a few constructs are not supported within story variables.
Functions, including static—i.e., non-instance—methods, due to a few issues.
Instance methods of classes are not affected by either issue, as they're never actually stored within story variables, being referenced from their classes' prototypes instead.
Expressions are simply units of code that yield values when evaluated. For example:
// Yields: true
true
// Yields: 1 (assuming that it is the first turn)
turns()
// Yields: 4
2 + 2
// Yields: "22"
"2" + "2"
Basic expressions simply consist of identifiers and literals—e.g., $a
, 69
, and "hello"
. Complex expressions consist of basic expressions joined together by operators—e.g., =
and +
.
While every valid expression—even those you might not expect—yields a value, there are essentially two types of expressions: those with side effects and those without. A side effect simply means that the evaluation of the expression modifies some state. For example:
// Yields: 5; Side effect: assigns 5 to the story variable $a
$a = 5
// Yields: 25 (assuming $x is 15); No side effects
$x + 10
In general, you can group expressions into categories based on what kind of value they yield and/or what side effects they cause. For example: (not an exhaustive list)
42
or 3.14
."Lulamoon"
or "5678"
.true
or false
.$a = 5
.You will, in all likelihood, use expressions most often within macros—e.g., <<set>>
, <<print>>
, <<if>>
, <<for>>
.
Operators join together operands, which are formed from either basic or complex expressions.
In both TwineScript and JavaScript there are binary and unary operators—n.b., Javascript also includes a ternary operator, the conditional operator. Binary operators require two operands, one before and one after the operator, while unary operators only require one operand, either before or after the operator.
Binary operator examples:
// operand1 OPERATOR operand2
2 + 2
$a = 5
Unary operator examples:
// operand OPERATOR
$i++
// OPERATOR operand
++$x
not $hasKey
Assignment operators assign a value to their left-hand operand based on the value of their right-hand operand.
Operator | Description | Example |
---|---|---|
to |
Assigns the value on the right-hand side of the operator to the left-hand side. |
|
Operator | Description | Example |
---|---|---|
= |
Assigns the value on the right-hand side of the operator to the left-hand side. |
|
+= |
Adds the value on the right-hand side of the operator to the current value on the left-hand side and assigns the result to the left-hand side. |
|
-= |
Subtracts the value on the right-hand side of the operator from the current value on the left-hand side and assigns the result to the left-hand side. |
|
*= |
Multiplies the current value on the left-hand side of the operator by the value on the right-hand side and assigns the result to the left-hand side. |
|
/= |
Divides the current value on the left-hand side of the operator by the value on the right-hand side and assigns the result to the left-hand side. |
|
%= |
Divides the current value on the left-hand side of the operator by the value on the right-hand side and assigns the remainder to the left-hand side. |
|
Comparison operators compare their operands and return a boolean value based on whether the comparison is true.
Operator | Description | Example |
---|---|---|
is |
Evaluates to true if both sides are strictly equal. |
|
isnot |
Evaluates to true if both sides are strictly not equal. |
|
eq |
Evaluates to true if both sides are equivalent. Not recommended, use the is operator. |
|
neq |
Evaluates to true if both sides are not equivalent. Not recommended, use the isnot operator. |
|
gt |
Evaluates to true if the left side is greater-than the right side. |
|
gte |
Evaluates to true if the left side is greater-than-or-equal-to the right side. |
|
lt |
Evaluates to true if the left side is less-than the right side. |
|
lte |
Evaluates to true if the left side is less-than-or-equal-to the right side. |
|
not |
Flips a true evaluation to false , and vice versa. |
|
and |
Evaluates to true if all subexpressions evaluate to true . |
|
or |
Evaluates to true if any subexpressions evaluate to true . |
|
def |
Evaluates to true if the right side is defined. See the precedence warning below. |
|
ndef |
Evaluates to true if the right side is not defined. See the precedence warning below. |
|
Warning:
The def
and ndef
operators have very low precedence, so it is strongly recommended that if you mix them with other operators, that you wrap them in parentheses—e.g., (def $style) and ($style is "girly")
.
Operator | Description | Example |
---|---|---|
=== |
Evaluates to true if both sides are strictly equal. |
|
!== |
Evaluates to true if both sides are strictly not equal. |
|
== |
Evaluates to true if both sides are equivalent. Not recommended, use the === operator. |
|
!= |
Evaluates to true if both sides are not equivalent. Not recommended, use the !== operator. |
|
> |
Evaluates to true if the left side is greater-than the right side. |
|
>= |
Evaluates to true if the left side is greater-than-or-equal-to the right side. |
|
< |
Evaluates to true if the left side is less-than the right side. |
|
<= |
Evaluates to true if the left side is less-than-or-equal-to the right side. |
|
! |
Flips a true evaluation to false , and vice versa. |
|
&& |
Evaluates to true if all subexpressions evaluate to true . |
|
|| |
Evaluates to true if any subexpressions evaluate to true . |
|
Macros fall into two broad categories based on the kind of arguments they accept: those that want an expression—e.g., <<set>>
and <<print>>
—and those that want discrete arguments separated by whitespace—e.g., <<link>>
and <<audio>>
. The documentation for each macro will tell you what it expects.
Those that want an expression are fairly straightforward, as you simply supply an expression.
The discrete argument type of macros are also fairly straightforward, most of the time, as you simply supply the requisite arguments separated by whitespace, which may include variables—as SugarCube automatically yields their values to the macro. There are cases, however, where things get a bit more complicated, namely: instances where you need to pass the name of a variable as an argument, rather than its value, and those where you want to pass the result of an expression as argument.
Passing the name of a variable as an argument is problematic because variable substitution occurs automatically in SugarCube macros. Meaning that when you pass a variable as an argument, its value is passed to the macro rather than its name.
Normally, this is exactly what you want to happen. Occasionally, however, macros will need the name of a variable rather than its value—e.g., data input macros like <<textbox>>
—so that they may modify the variable. To resolve these instances, you will need to quote the name of the variable—i.e., instead of passing $pie
as normal, you'd pass "$pie"
. These, rare, instances are noted in the macros' documentation and shown in their examples.
Passing the result of an expression as an argument is problematic for a couple of reasons: because the macro argument parser doesn't treat arguments as expressions by default and because it separates arguments with whitespace.
Normally, those aren't issues as you should not need to use the result of an expression as an argument terribly often. To resolve instances where you do, however, you'll want to use either a temporary variable or a backquote expression.
For example, the following will not work because the macro parser will think that you're passing five discrete arguments, rather than a single expression:
<<link "Wake " + $friend + ".">> … <</link>>
You could solve the problem by using a temporary variable to hold the result of the expression, then pass that to the macro. For example:
<<set _text to "Wake " + $friend + ".">>\
<<link _text>> … <</link>>
A better solution, however, would be to use a backquote1 (`
) expression, which is really just a special form of quoting available in macro arguments that causes the contents of the backquotes to be evaluated and then yields the result as a singular argument. For example:
<<link `"Wake " + $friend + "."`>> … <</link>>
~
) on keyboards.<<capture variableList>> … <</capture>>
Captures story $variables and temporary _variables, creating localized versions of their values within the macro body.
Note:
Use of this macro is only necessary when you need to localize a variable's value for use with an asynchronous macro—i.e., a macro whose contents are executed at some later time, rather than when it's invoked; e.g., interactive macros, <<repeat>>
, <<timed>>
. Generally, this means only when the variable's value will change between the time the asynchronous macro is invoked and when it's activated—e.g., a loop variable.
v2.14.0
: Introduced.variableList
: A list of story $variables and/or temporary _variables.→ Using <<capture>> to localize a temporary loop variable for use within a <<linkappend>>
<<set _what to [
"a crab rangoon",
"a gaggle of geese",
"an aardvark",
"the world's smallest violin"
]>>
<<for _i to 0; _i lt _what.length; _i++>>
<<capture _i>>
I spy with my little <<linkappend "eye" t8n>>, _what[_i]<</linkappend>>.
<</capture>>
<</for>>
→ Capturing several variables at once
<<capture $aStoryVar, $anotherStoryVar, _aTempVar>> … <</capture>>
<<set expression>>
Sets story $variables and temporary _variables based on the given expression.
v2.0.0
: Introduced.expression
: A valid expression. See Variables, Expressions, Operators for more information.<<set $cheese to "a nice, sharp cheddar">> → Assigns "a nice, sharp cheddar" to story variable $cheese
<<set $chestEmpty to true>> → Assigns boolean true to story variable $chestEmpty
<<set $sum to $a + $b>> → Assigns the summation of story variables $a and $b to $sum
<<set $gold to $gold + 5>> → Adds 5 to the value of story variable $gold
<<set _counter to _counter + 1>> → Adds 1 to the value of temporary variable _counter
<<set $cheese = "a nice, sharp cheddar">> → Assigns "a nice, sharp cheddar" to story variable $cheese
<<set $chestEmpty = true>> → Assigns boolean true to story variable $chestEmpty
<<set $sum = $a + $b>> → Assigns the summation of story variables $a and $b to $sum
<<set $gold += 5>> → Adds 5 to the value of story variable $gold
<<set _counter += 1>> → Adds 1 to the value of temporary variable _counter
<<unset variableList>>
Unsets story $variables, temporary _variables, and properties of objects stored within either.
v2.0.0
: Introduced.v2.37.0
: Added ability to unset object properties.variableList
: A list of story $variables, temporary variables, or properties of objects stored within either.Basic usage, unsetting story and temporary variables.
<<unset $rumors>>
<<unset _npc>>
<<unset $rumors, _npc, _choices, $job>>
Unsetting object properties.
<<unset _choices.b>>
<<unset $towns['port ulster'].rumors>>
<<unset _choices.b, $towns['port ulster'].rumors, $pc.notes, _park.rides['wheel of death']>>
<<run expression>>
Functionally identical to <<set>>
. Intended to be mnemonically better for uses where the expression is arbitrary code, rather than variables to set—i.e., <<run>>
to run code, <<set>>
to set variables.
<<script [language]>> … <</script>>
Silently executes its contents as either JavaScript or TwineScript code (default: JavaScript).
Note:
The predefined variable output
, which is a reference to a local content buffer, is available for use within the macro's code contents. Once the code has been fully executed, the contents of the buffer, if any, will be output.
v2.0.0
: Introduced.v2.37.0
: Added optional language
argument.language
: (optional) The language to evaluate the given code as; case-insensitive options: JavaScript
, TwineScript
. If omitted, defaults to JavaScript
.<<script>>
/* JavaScript code */
<</script>>
<<script TwineScript>>
/* TwineScript code */
<</script>>
<<script>>
/*
When accessing managed variables in JavaScript, it's often a good idea
to cache references to whichever variable store you happen to be using.
*/
const svars = State.variables;
const tvars = State.temporary;
/* Access the `$items` story variable. */
if (svars.items.includes('bloody knife')) {
/* Has a bloody knife. */
}
/* Access the `_hit` temporary variable. */
tvars.hit += 1;
<</script>>
<<script TwineScript>>
/* Access the `$items` story variable. */
if ($items.includes('bloody knife')) {
/* Has a bloody knife. */
}
/* Access the `_hit` temporary variable. */
_hit += 1;
<</script>>
There's no difference between JavaScript and TwineScript here.
<<script>>
/* Parse some markup and append the result to the output buffer. */
$(output).wiki("Cry 'Havoc!', and let slip the //ponies// of ''friendship''.");
<</script>>
<<= expression>>
Outputs a string representation of the result of the given expression. This macro is an alias for <<print>>
.
Tip: If you only need to print the value of a TwineScript variable, then you may simply include it in your normal passage text and it will be printed automatically via the naked variable markup.
v2.0.0
: Introduced.expression
: A valid expression. See Expressions for more information.→ Assuming $gold is 5
You found <<= $gold>> gold. → Outputs: You found 5 gold.
→ Assuming $weight is 74.6466266
You weigh <<= $weight.toFixed(2)>> kg. → Outputs: You weigh 74.65 kg.
<<- expression>>
Outputs a string representation of the result of the given expression. This macro is functionally identical to <<print>>
, save that it also encodes HTML special characters in the output.
Tip: If you only need to print the value of a TwineScript variable, then you may simply include it in your normal passage text and it will be printed automatically via the naked variable markup.
v2.0.0
: Introduced.expression
: A valid expression. See Expressions for more information.→ Assuming $gold is 5
You found <<- $gold>> gold. → Outputs: You found 5 gold.
→ Assuming $weight is 74.6466266
You weigh <<- $weight.toFixed(2)>> kg. → Outputs: You weigh 74.65 kg.
<<do [tag tags] [element tag]>> … <</do>>
Displays its contents. Listens for <<redo>>
macro commands upon which it updates its contents.
v2.37.0
: Introduced.tag
tags
: (optional) The space separated list of tags used to filter <<redo>>
commands.element
tag
: (optional) The element to use as the content container—e.g., div
and span
. If omitted, defaults to span
.<<set $money to 10>>
''Money:'' <<do>>$money<</do>>
<<link "Update money display">>
<<set $money += 10>>
<<redo>>
<</link>>
<<set $key to "">> /* no key */
<<do>>
<<if $key>>
You have the $key key.
<<else>>
You do not have a key.
<</if>>
<</do>>
<<link "Update key display">>
<<set $key to ["", "red", "blue", "skull"].random()>>
<<redo>>
<</link>>
''Foo:'' <<do tag "foo foobar">><<= ["fee", "fie", "foe", "fum"].random()>><</do>>
''Bar:'' <<do tag "bar foobar">><<= ["alfa", "bravo", "charlie", "delta"].random()>><</do>>
<<link "Update foo">>
<<redo "foo">>
<</link>>
<<link "Update bar">>
<<redo "bar">>
<</link>>
<<link "Update foo & bar (1)">>
<<redo "foo bar">>
<</link>>
<<link "Update foo & bar (2)">>
<<redo "foobar">>
<</link>>
<<include passageName [elementName]>>
<<include linkMarkup [elementName]>>
Outputs the contents of the passage with the given name, optionally wrapping it within an HTML element. May be called either with the passage name or with a link markup.
v2.15.0
: Introduced.passageName
: The name of the passage to include.elementName
: (optional) The HTML element to wrap the included passage in. If used, the element will include the passage's name normalized into a class name. See CSS passage conversions for more information.linkMarkup
: The link markup to use (regular syntax only, no setters).elementName
: Identical to the passage name form.<<include "Go West">> → Include the passage "Go West"
<<include [[Go West]]>> → Include the passage "Go West"
<<include "Go West" "div">> → Include the passage "Go West", wrapping it within a <div>
<<include [[Go West]] "div">> → Include the passage "Go West", wrapping it within a <div>
<<nobr>> … <</nobr>>
Executes its contents and outputs the result, after removing leading/trailing newlines and replacing all remaining sequences of newlines with single spaces.
Note:
The nobr
special tag and Config.passages.nobr
setting applies the same processing to an entire passage or all passages, respectively. The line continuation markup performs a similar function, though in a slightly different way.
v2.0.0
: Introduced.→ Given: $feeling eq "blue", outputs: I'd like a blueberry pie.
I'd like a <<nobr>>
<<if $feeling eq "blue">>
blueberry
<<else>>
cherry
<</if>>
<</nobr>> pie.
<<print expression>>
Outputs a string representation of the result of the given expression.
Tip: If you only need to print the value of a TwineScript variable, then you may simply include it in your normal passage text and it will be printed automatically via the naked variable markup.
v2.0.0
: Introduced.expression
: A valid expression. See Expressions for more information.→ Assuming $gold is 5
You found <<print $gold>> gold. → Outputs: You found 5 gold.
→ Assuming $weight is 74.6466266
You weigh <<print $weight.toFixed(2)>> kg. → Outputs: You weigh 74.65 kg.
<<redo [tags]>>
Causes one or more <<do>>
macros to update their contents.
v2.37.0
: Introduced.tags
: (optional) The space separated list of tags corresponding to tagged <<do>>
macros to send update commands to. If omitted, sends update commands to all <<do>>
macros.See:
<<do>>
macro for examples.
<<silent>> … <</silent>>
Causes any output generated within its body to be discarded, except for errors (which will be displayed). Generally, only really useful for formatting blocks of macros for ease of use/readability, while ensuring that no output is generated, from spacing or whatnot.
v2.37.0
: Introduced.→ Basic
<<silent>>
You'll never see any of this!
<</silent>>
→ Hiding the guts of a countdown timer
<<set $seconds to 10>>\
Countdown: <span id="countdown">$seconds seconds remaining</span>!\
<<silent>>
<<repeat 1s>>
<<set $seconds to $seconds - 1>>
<<if $seconds gt 0>>
<<replace "#countdown">>$seconds seconds remaining<</replace>>
<<else>>
<<replace "#countdown">>Too Late<</replace>>
/* do something useful here */
<<stop>>
<</if>>
<</repeat>>
<</silent>>
<<type speed [start delay] [class classes] [element tag] [id ID] [keep|none] [skipkey key]>>
…
<</type>>
Outputs its contents a character—technically, a code point—at a time, mimicking a teletype/typewriter. Can type most content: links, markup, macros, etc.
Warning:
Interactions with macros or other code that inject content only after some external action or period—e.g., <<linkreplace>>
, <<timed>>
, etc.—may or may not behave as you'd expect. Testing is strongly advised.
See Also:
Config.macros.typeSkipKey
, Config.macros.typeVisitedPassages
, <<type>>
Events.
v2.32.0
: Introduced.v2.33.0
: Added class
, element
, and id
options and macro-type-done
class.v2.33.1
: Added skipkey
option.v2.37.0
: Updated speed
argument so 0s
and 0ms
skip.speed
: The rate at which characters are typed, as a valid CSS time value—e.g., 1s
and 40ms
. Values in the range 20–60ms
are a good starting point. Values of 0s
and 0ms
cause typing to finish immediately.start
delay
: (optional) The amount of time to delay the start of typing, as a valid CSS time value—e.g., 5s
and 500ms
. If omitted, defaults to 400ms
.class
classes
: (optional) The space separated list of classes to be added to the typing container.element
tag
: (optional) The element to use as the typing container—e.g., div
and span
. If omitted, defaults to div
.id
ID
: (optional) The unique ID to be assigned to the typing container.keep
: (optional) Keyword, used to signify that the cursor should be kept after typing is complete.none
: (optional) Keyword, used to signify that the cursor should not be used at all.skipkey
: (optional) The key used to cause typing to finish immediately. If omitted, defaults to the value of Config.macros.typeSkipKey
.<<type 40ms>>
Type characters from this content every 40 milliseconds. Including [[links]] and ''other markup''!
<</type>>
<<type 40ms start 2s>>
Type characters from this content every 40 milliseconds, starting after a 2 second delay.
<</type>>
<<type 40ms class "foo bar">>
Type characters from this content every 40 milliseconds, adding classes to the typing container.
<</type>>
<<type 40ms element "span">>
Type characters from this content every 40 milliseconds, using a <span> as the typing container.
<</type>>
<<type 40ms id "type01">>
Type characters from this content every 40 milliseconds, assigning an ID to the typing container.
<</type>>
<<type 40ms keep>>
Type characters from this content every 40 milliseconds, keeping the cursor after typing is complete.
<</type>>
<<type 40ms skipkey "Control">>
Type characters from this content every 40 milliseconds, using the Control (CTRL) key as the skip key.
<</type>>
The typed text has no default styling. If you want to change the font or color, then you'll need to change the styling of the macro-type
class. For example:
.macro-type {
color: limegreen;
font-family: monospace, monospace;
}
There's also a macro-type-done
class that is added to text that has finished typing, which may be used to style it differently from actively typing text.
The default cursor is the block element character Right Half Block (U+2590) and it has no default font or color styling. If you want to change the font, color, or character, then you'll need to change the styling of the :after
pseudo-element of the macro-type-cursor
class. For example:
.macro-type-cursor:after {
color: limegreen;
content: "\269C\FE0F"; /* Fleur-de-lis emoji */
font-family: monospace, monospace;
}
<<silently>> … <</silently>>
Deprecated:
This macro has been deprecated and should no longer be used. See the <<silent>>
macro for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of <<silent>>
.<<if conditional>> … [<<elseif conditional>> …] [<<else>> …] <</if>>
Executes its contents if the given conditional expression evaluates to true
. If the condition evaluates to false
and an <<elseif>>
or <<else>>
exists, then other contents can be executed.
Note:
SugarCube does not trim whitespace from the contents of <<if>>
macros, so that authors don't have to resort to various kludges to get whitespace where they want it. This means, however, that extra care must be taken when writing them to ensure that unwanted whitespace is not created within the final output.
v2.0.0
: Introduced.conditional
: A valid conditional expression, evaluating to either true
or false
. See Expressions and Operators for more information.<<if $daysUntilLoanDue is 0>><<include "Panic">><</if>>
<<if $cash lt 5>>
I'm sorry, ma'am, but you don't have enough for the pie.
<<else>>
<<set $cash -= 5, $hasMeatPie = true>>
One meat pie, fresh out of the oven, coming up!
<</if>>
<<if $affection gte 75>>
I love you!
<<elseif $affection gte 50>>
I like you.
<<elseif $affection gte 25>>
I'm okay with you.
<<else>>
Get out of my face.
<</if>>
<<if $hullBreached>>
<<if $wearingHardSuit>>
<<include "That was close">>
<<elseif $wearingSoftSuit>>
<<include "Hole in suit">>
<<else>>
<<include "You die">>
<</if>>
<</if>>
<<for [conditional]>> … <</for>>
<<for [init] ; [conditional] ; [post]>> … <</for>>
<<for [[keyVariable ,] valueVariable] range collection>> … <</for>>
Repeatedly executes its contents. There are three forms: a conditional-only form, a 3-part conditional form, and a range form.
See Also:
<<break>>
and <<continue>>
.
v2.0.0
: Introduced.v2.20.0
: Added range form.v2.37.0
: Added range over integers and made range value variable optional._i
.Executes its contents while the given conditional expression evaluates to true
. If no conditional expression is given, it is equivalent to specifying true
.
Note:
The maximum number of loop iterations in the conditional forms is not unlimited by default, however, it is configurable. See Config.macros.maxLoopIterations
for more information.
Iterates through all enumerable entries of the given collection. For each iteration, it assigns the key/value pair of the associated entry in the collection to the iteration variables and then executes its contents. Valid collection types are: arrays, generic objects, integers, maps, sets, and strings.
init
: (optional) A valid expression, evaluated once at loop initialization. Typically used to initialize counter variable(s). See <<set>>
for more information.conditional
: (optional) A valid conditional expression, evaluated prior to each loop iteration. As long as the expression evaluates to true
, the loop is executed. See <<if>>
for more information.post
: (optional) A valid expression, evaluated after each loop iteration. Typically used to update counter variable(s). See <<set>>
for more information.keyVariable
: (optional) A story or temporary variable that will be set to the iteration key.valueVariable
: (optional) A story or temporary variable that will be set to the iteration value.range
: Keyword, used to signify that the loop is using the range form syntax.collection
: An expression that yields a valid collection type (see below), evaluated once at loop initialization.Collection type | Iteration: key, value |
---|---|
Arrays, Integers, & Sets | Member: index, value |
Generic objects | Property: name, value |
Maps | Entry: key, value |
Strings | Code point: start index, value |
Note: Strings are iterated by Unicode code point, however, due to historic reasons they are comprised of, and indexed by, individual UTF-16 code units. This means that some code points may span multiple code units—e.g., the character 💩 is one code point, but two code units.
Iterating over an array.
/* Given the following: */
<<set $pies to ["Apple", "Blueberry", "Cherry", "Pecan", "Raspberry"]>>
<<for _i to 0; _i lt $pies.length; _i++>>
<<print _i + 1>>. $pies[_i]
<</for>>
/* Outputs */
1. Apple
2. Blueberry
3. Cherry
4. Pecan
5. Raspberry
Ranging over an array.
/* Given the following: */
<<set $pies to ["Apple", "Blueberry", "Cherry", "Pecan", "Raspberry"]>>
<<for _i, _name range $pies>>
<<print _i + 1>>. _name
<</for>>
/* Outputs */
1. Apple
2. Blueberry
3. Cherry
4. Pecan
5. Raspberry
Ranging over an integer.
<<for _value range 5>>
<<print _value + 1>>.
<</for>>
/* Outputs */
1.
2.
3.
4.
5.
<<break>>
Used within <<for>>
macros. Terminates the execution of the current <<for>>
.
v2.0.0
: Introduced.<<continue>>
Used within <<for>>
macros. Terminates the execution of the current iteration of the current <<for>>
and begins execution of the next iteration.
Note: May eat line-breaks in certain situations.
v2.0.0
: Introduced.<<switch expression>>
[<<case valueList>> …]
[<<default>> …]
<</switch>>
Evaluates the given expression and compares it to the value(s) within its <<case>>
children. The value(s) within each case are compared to the result of the expression given to the parent <<switch>>
. Upon a successful match, the matching case will have its contents executed. If no cases match and an optional <<default>>
case exists, which must be the final case, then its contents will be executed. At most one case will execute.
Note:
SugarCube does not trim whitespace from the contents of <<case>>
/<<default>>
macros, so that authors don't have to resort to various kludges to get whitespace where they want it. However, this means that extra care must be taken when writing them to ensure that unwanted whitespace is not created within the final output.
v2.7.2
: Introduced.<<switch>>
expression
: A valid expression. See Expressions for more information.<<case>>
valueList
: A space separated list of values to compare against the result of the switch expression.→ Without a default case
<<switch $hairColor>>
<<case "red" "auburn">>
You ginger.
<<case "black" "brown">>
Dark haired, eh?
<<case "blonde">>
You may have more fun.
<</switch>>
→ With a default case (assume the passage is about a waterfall)
<<switch visited()>>
<<case 1>>
You gaze in wonder at the magnificent waterfall for the first time, awestruck by its natural beauty.
<<case 2 3>>
You once again gaze upon the magnificent waterfall.
<<case 4 5>>
Yet again, you find yourself looking upon the waterfall.
<<default>>
Oh, look. It's that waterfall again. Meh.
<</switch>>
Interactive macros are both asynchronous and require interaction from the player. Thus, there are some potential pitfalls to consider:
<<capture>>
macro due to their asynchronous nature.<<button linkText [passageName]>> … <</button>>
<<button linkMarkup>> … <</button>>
<<button imageMarkup>> … <</button>>
Creates a button that silently executes its contents when clicked, optionally forwarding the player to another passage. May be called with either the link text and passage name as separate arguments, a link markup, or an image markup.
See: Interactive macro warning.
Note:
This macro is functionally identical to <<link>>, save that it uses a button element (<button>
) rather than an anchor element (<a>
).
v2.8.0
: Introduced.linkText
: The text of the link. May contain markup.passageName
: (optional) The name of the passage to go to.linkMarkup
: The link markup to use (regular syntax only, no setters).imageMarkup
: The image markup to use (regular syntax only, no setters).→ Without forwarding: a very basic statistic setting example
Strength: <<set $pcStr to 10>><span id="stats-str"><<print $pcStr>></span> \
( <<button "[+]">><<set $pcStr++>><<replace "#stats-str">><<print $pcStr>><</replace>><</button>> \
| <<button "[-]">><<set $pcStr-->><<replace "#stats-str">><<print $pcStr>><</replace>><</button>> )
→ With forwarding: execute a script, then go to the specified passage
<<button "Onward, Reginald!" "On with the story">><<script>>/* (code) */<</script>><</button>>
<<button [[Onward, Reginald!|On with the story]]>><<script>>/* (code) */<</script>><</button>>
<<button [img[onward.jpg][On with the story]]>><<script>>/* (code) */<</script>><</button>>
<<checkbox receiverName uncheckedValue checkedValue [autocheck|checked]>>
Creates a checkbox, used to modify the value of the variable with the given name.
See: Interactive macro warning.
v2.0.0
: Introduced.v2.32.0
: Added autocheck
keyword.receiverName
: The name of the variable to modify, which must be quoted—e.g., "$foo"
. Object and array property references are also supported—e.g., "$foo.bar"
, "$foo['bar']"
, & "$foo[0]"
.uncheckedValue
: The value set by the checkbox when unchecked.checkedValue
: The value set by the checkbox when checked.autocheck
: (optional) Keyword, used to signify that the checkbox should be automatically set to the checked state based on the current value of the receiver variable. NOTE: Automatic checking may fail on non-primitive values—i.e., on arrays and objects.checked
: (optional) Keyword, used to signify that the checkbox should be in the checked state.What pies do you enjoy?
* <<checkbox "$pieBlueberry" false true autocheck>> Blueberry?
* <<checkbox "$pieCherry" false true autocheck>> Cherry?
* <<checkbox "$pieCoconutCream" false true autocheck>> Coconut cream?
What pies do you enjoy?
* <<checkbox "$pieBlueberry" false true checked>> Blueberry?
* <<checkbox "$pieCherry" false true>> Cherry?
* <<checkbox "$pieCoconutCream" false true checked>> Coconut cream?
<label>
elementTip:
For accessibility reasons, it's recommended that you wrap each <<checkbox>>
and its accompanying text within a <label>
element. Doing so allows interactions with the text to also trigger its <<checkbox>>
.
What pies do you enjoy?
* <label><<checkbox "$pieBlueberry" false true autocheck>> Blueberry?</label>
* <label><<checkbox "$pieCherry" false true autocheck>> Cherry?</label>
* <label><<checkbox "$pieCoconutCream" false true autocheck>> Coconut cream?</label>
What pies do you enjoy?
* <label><<checkbox "$pieBlueberry" false true checked>> Blueberry?</label>
* <label><<checkbox "$pieCherry" false true>> Cherry?</label>
* <label><<checkbox "$pieCoconutCream" false true checked>> Coconut cream?</label>
<<cycle receiverName [once] [autoselect]>>
[<<option label [value [selected]]>> …]
[<<optionsfrom collection>> …]
<</cycle>>
Creates a cycling link, used to modify the value of the variable with the given name. The cycling options are populated via <<option>>
and/or <<optionsfrom>>
.
See: Interactive macro warning.
v2.29.0
: Introduced.v2.36.0
: Fixed the selected
keyword and added the once
keyword.<<cycle>>
receiverName
: The name of the variable to modify, which must be quoted—e.g., "$foo"
. Object and array property references are also supported—e.g., "$foo.bar"
, "$foo['bar']"
, & "$foo[0]"
.once
: (optional) Keyword, used to signify that the cycle should stop upon reaching the last option and deactivate itself. NOTE: Since you likely want to start at the first option when using this keyword, you should either not select an option, so it defaults to the first, or, if you do, select the first option only.autoselect
: (optional) Keyword, used to signify that an option should be automatically selected as the cycle default based on the current value of the receiver variable. NOTE: Automatic option selection will fail on non-primitive values—i.e., on arrays and objects.<<option>>
label
: The label shown by the cycling link for the option.value
: (optional) The value set by the cycling link when the option is selected. If omitted, the label will be used as the value.selected
: (optional) Keyword, used to signify that the option should be the cycle default. Only one option may be so selected. If no options are selected as the default, the cycling link will default to the first option, unless the cycle autoselect
keyword is specified. NOTE: If specified, the value
argument is not optional.<<optionsfrom>>
collection
: An expression that yields a valid collection type.
Collection type | Option: label, value |
---|---|
Arrays & Sets | Member: value, value |
Generic objects | Property: name, value |
Maps | Entry: key, value |
<<option>>
The answer to the //Ultimate Question of Life, the Universe, and Everything// is?
<<cycle "$answer" autoselect>>
<<option "Towel">>
<<option "π" 3.14159>>
<<option 42>>
<<option 69>>
<<option "∞" Infinity>>
<</cycle>>
<<optionsfrom>>
with an array→ Given: _pieOptions = ["blueberry", "cherry", "coconut cream"]
What's your favorite pie?
<<cycle "$pie" autoselect>>
<<optionsfrom _pieOptions>>
<</cycle>>
<<optionsfrom>>
with an generic object→ Given: _pieOptions = { "Blueberry" : "blueberry", "Cherry" : "cherry", "Coconut cream" : "coconut cream" }
What's your favorite pie?
<<cycle "$pie" autoselect>>
<<optionsfrom _pieOptions>>
<</cycle>>
once
keywordYou see a large red, candy-like button.
<<cycle "$presses" once>>
<<option "Should you press it?" 0>>
<<option "Nothing happened. Press it again?" 1>>
<<option "Again?" 2>>
<<option "That time it locked into place with a loud click and began to glow ominously." 3>>
<</cycle>>
<<link linkText [passageName]>> … <</link>>
<<link linkMarkup>> … <</link>>
<<link imageMarkup>> … <</link>>
Creates a link that silently executes its contents when clicked, optionally forwarding the player to another passage. May be called with either the link text and passage name as separate arguments, a link markup, or an image markup.
See: Interactive macro warning.
Note: If you simply need a passage link that modifies variables, both the link markup and image markup offer setter variants.
v2.8.0
: Introduced.linkText
: The text of the link. May contain markup.passageName
: (optional) The name of the passage to go to.linkMarkup
: The link markup to use (regular syntax only, no setters).imageMarkup
: The image markup to use (regular syntax only, no setters).→ Without forwarding: a very basic statistic setting example
Strength: <<set $pcStr to 10>><span id="stats-str"><<print $pcStr>></span> \
( <<link "[+]">><<set $pcStr++>><<replace "#stats-str">><<print $pcStr>><</replace>><</link>> \
| <<link "[-]">><<set $pcStr-->><<replace "#stats-str">><<print $pcStr>><</replace>><</link>> )
→ With forwarding: execute a script, then go to the specified passage
<<link "Onward, Reginald!" "On with the story">><<script>>/* (code) */<</script>><</link>>
<<link [[Onward, Reginald!|On with the story]]>><<script>>/* (code) */<</script>><</link>>
<<link [img[onward.jpg][On with the story]]>><<script>>/* (code) */<</script>><</link>>
<<linkappend linkText [transition|t8n]>> … <</linkappend>>
Creates a single-use link that deactivates itself and appends its contents to its link text when clicked. Essentially, a combination of <<link>>
and <<append>>
.
See: Interactive macro warning.
v2.0.0
: Introduced.linkText
: The text of the link. May contain markup.transition
: (optional) Keyword, used to signify that a CSS transition should be applied to the incoming insertions.t8n
: (optional) Keyword, alias for transition
.→ Without a transition
We—We should <<linkappend "take">> away their METAL BAWKSES<</linkappend>>!
→ With a transition
I spy with my little <<linkappend "eye" t8n>>, a crab rangoon<</linkappend>>.
<<linkprepend linkText [transition|t8n]>> … <</linkprepend>>
Creates a single-use link that deactivates itself and prepends its contents to its link text when clicked. Essentially, a combination of <<link>>
and <<prepend>>
.
See: Interactive macro warning.
v2.0.0
: Introduced.linkText
: The text of the link. May contain markup.transition
: (optional) Keyword, used to signify that a CSS transition should be applied to the incoming insertions.t8n
: (optional) Keyword, alias for transition
.→ Without a transition
You see a <<linkprepend "robot">>GIANT <</linkprepend>>.
→ With a transition
I <<linkprepend "like" t8n>>do not <</linkprepend>> lemons.
<<linkreplace linkText [transition|t8n]>> … <</linkreplace>>
Creates a single-use link that deactivates itself and replaces its link text with its contents when clicked. Essentially, a combination of <<link>>
and <<replace>>
.
See: Interactive macro warning.
v2.0.0
: Introduced.linkText
: The text of the link. May contain markup.transition
: (optional) Keyword, used to signify that a CSS transition should be applied to the incoming insertions.t8n
: (optional) Keyword, alias for transition
.→ Without a transition
I'll have a <<linkreplace "cupcake">>slice of key lime pie<</linkreplace>>, please.
→ With a transition
<<linkreplace "You'll //never// take me alive!" t8n>>On second thought, don't hurt me.<</linkreplace>>
<<listbox receiverName [autoselect]>>
[<<option label [value [selected]]>> …]
[<<optionsfrom collection>> …]
<</listbox>>
Creates a listbox, used to modify the value of the variable with the given name. The list options are populated via <<option>>
and/or <<optionsfrom>>
.
See: Interactive macro warning.
v2.26.0
: Introduced.v2.27.0
: Added autoselect
keyword.v2.28.0
: <<optionsFrom>>
child tag.v2.28.1
: Fixed name of <<optionsfrom>>
child tag, which was erroneously added as <<optionsFrom>>
in v2.28.0
.v2.29.0
: Made the <<option>>
child tag's value
argument optional.v2.36.0
: Fixed the selected
keyword.<<listbox>>
receiverName
: The name of the variable to modify, which must be quoted—e.g., "$foo"
. Object and array property references are also supported—e.g., "$foo.bar"
, "$foo['bar']"
, & "$foo[0]"
.autoselect
: (optional) Keyword, used to signify that an option should be automatically selected as the listbox default based on the current value of the receiver variable. NOTE: Automatic option selection will fail on non-primitive values—i.e., on arrays and objects.<<option>>
label
: The label shown by the listbox for the option.value
: (optional) The value set by the listbox when the option is selected. If omitted, the label will be used as the value.selected
: (optional) Keyword, used to signify that the option should be the listbox default. Only one option may be so selected. If no options are selected as the default, the listbox will default to the first option, unless the listbox autoselect
keyword is specified. NOTE: If specified, the value
argument is not optional.<<optionsfrom>>
collection
: An expression that yields a valid collection type.
Collection type | Option: label, value |
---|---|
Arrays & Sets | Member: value, value |
Generic objects | Property: name, value |
Maps | Entry: key, value |
<<option>>
The answer to the //Ultimate Question of Life, the Universe, and Everything// is?
<<listbox "$lbanswer" autoselect>>
<<option "Towel">>
<<option "π" 3.14159>>
<<option 42>>
<<option 69>>
<<option "∞" Infinity>>
<</listbox>>
<<optionsfrom>>
with an array→ Given: _pieOptions = ["blueberry", "cherry", "coconut cream"]
What's your favorite pie?
<<listbox "$pie" autoselect>>
<<optionsfrom _pieOptions>>
<</listbox>>
<<optionsfrom>>
with an generic object→ Given: _pieOptions = { "Blueberry" : "blueberry", "Cherry" : "cherry", "Coconut cream" : "coconut cream" }
What's your favorite pie?
<<listbox "$pie" autoselect>>
<<optionsfrom _pieOptions>>
<</listbox>>
<<numberbox receiverName defaultValue [passage] [autofocus]>>
Creates a number input box, used to modify the value of the variable with the given name, optionally forwarding the player to another passage.
See: Interactive macro warning.
v2.32.0
: Introduced.receiverName
: The name of the variable to modify, which must be quoted—e.g., "$foo"
. Object and array property references are also supported—e.g., "$foo.bar"
, "$foo['bar']"
, & "$foo[0]"
.defaultValue
: The default value of the number box.passage
: (optional) The name of the passage to go to if the return/enter key is pressed. May be called either with the passage name or with a link markup.autofocus
: (optional) Keyword, used to signify that the number box should automatically receive focus. Only use the keyword once per page; attempting to focus more than one element is undefined behavior.→ Creates a number box that modifies $wager
Wager how much on Buttstallion in the race? <<numberbox "$wager" 100>>
→ Creates an automatically focused number box that modifies $wager
Wager how much on Buttstallion in the race? <<numberbox "$wager" 100 autofocus>>
→ Creates a number box that modifies $wager and forwards to the "Result" passage
Wager how much on Buttstallion in the race? <<numberbox "$wager" 100 "Result">>
→ Creates an automatically focused number box that modifies $wager and forwards to the "Result" passage
Wager how much on Buttstallion in the race? <<numberbox "$wager" 100 "Result" autofocus>>
<<radiobutton receiverName checkedValue [autocheck|checked]>>
Creates a radio button, used to modify the value of the variable with the given name. Multiple <<radiobutton>>
macros may be set up to modify the same variable, which makes them part of a radio button group.
See: Interactive macro warning.
v2.0.0
: Introduced.v2.32.0
: Added autocheck
keyword.receiverName
: The name of the variable to modify, which must be quoted—e.g., "$foo"
. Object and array property references are also supported—e.g., "$foo.bar"
, "$foo['bar']"
, & "$foo[0]"
.checkedValue
: The value set by the radio button when checked.autocheck
: (optional) Keyword, used to signify that the radio button should be automatically set to the checked state based on the current value of the receiver variable. NOTE: Automatic checking may fail on non-primitive values—i.e., on arrays and objects.checked
: (optional) Keyword, used to signify that the radio button should be in the checked state. NOTE: Only one radio button in a group—i.e., those using the same receiver variable—should be so checked.What's your favorite pie?
* <<radiobutton "$pie" "blueberry" autocheck>> Blueberry?
* <<radiobutton "$pie" "cherry" autocheck>> Cherry?
* <<radiobutton "$pie" "coconut cream" autocheck>> Coconut cream?
What's your favorite pie?
* <<radiobutton "$pie" "blueberry" checked>> Blueberry?
* <<radiobutton "$pie" "cherry">> Cherry?
* <<radiobutton "$pie" "coconut cream">> Coconut cream?
<label>
elementTip:
For accessibility reasons, it's recommended that you wrap each <<radiobutton>>
and its accompanying text within a <label>
element. Doing so allows interactions with the text to also trigger its <<radiobutton>>
.
What's your favorite pie?
* <label><<radiobutton "$pie" "blueberry" autocheck>> Blueberry?</label>
* <label><<radiobutton "$pie" "cherry" autocheck>> Cherry?</label>
* <label><<radiobutton "$pie" "coconut cream" autocheck>> Coconut cream?</label>
What's your favorite pie?
* <label><<radiobutton "$pie" "blueberry" checked>> Blueberry?</label>
* <label><<radiobutton "$pie" "cherry">> Cherry?</label>
* <label><<radiobutton "$pie" "coconut cream">> Coconut cream?</label>
<<textarea receiverName defaultValue [autofocus]>>
Creates a multiline text input block, used to modify the value of the variable with the given name.
See: Interactive macro warning.
v2.0.0
: Introduced.receiverName
: The name of the variable to modify, which must be quoted—e.g., "$foo"
. Object and array property references are also supported—e.g., "$foo.bar"
, "$foo['bar']"
, & "$foo[0]"
.defaultValue
: The default value of the text block.autofocus
: (optional) Keyword, used to signify that the text block should automatically receive focus. Only use the keyword once per page; attempting to focus more than one element is undefined behavior.→ Creates a text block that modifies $pieEssay
Write a short essay about pies:
<<textarea "$pieEssay" "">>
→ Creates an automatically focused text block that modifies $pieEssay
Write a short essay about pies:
<<textarea "$pieEssay" "" autofocus>>
<<textbox receiverName defaultValue [passage] [autofocus]>>
Creates a text input box, used to modify the value of the variable with the given name, optionally forwarding the player to another passage.
See: Interactive macro warning.
v2.0.0
: Introduced.receiverName
: The name of the variable to modify, which must be quoted—e.g., "$foo"
. Object and array property references are also supported—e.g., "$foo.bar"
, "$foo['bar']"
, & "$foo[0]"
.defaultValue
: The default value of the text box.passage
: (optional) The name of the passage to go to if the return/enter key is pressed. May be called either with the passage name or with a link markup.autofocus
: (optional) Keyword, used to signify that the text box should automatically receive focus. Only use the keyword once per page; attempting to focus more than one element is undefined behavior.→ Creates a text box that modifies $pie
What's your favorite pie? <<textbox "$pie" "Blueberry">>
→ Creates an automatically focused text box that modifies $pie
What's your favorite pie? <<textbox "$pie" "Blueberry" autofocus>>
→ Creates a text box that modifies $pie and forwards to the "Cakes" passage
What's your favorite pie? <<textbox "$pie" "Blueberry" "Cakes">>
→ Creates an automatically focused text box that modifies $pie and forwards to the "Cakes" passage
What's your favorite pie? <<textbox "$pie" "Blueberry" "Cakes" autofocus>>
<<back [linkText [passageName]]>>
<<back linkMarkup>>
<<back imageMarkup>>
Creates a link that undoes past moments within the story history. May be called with, optional, the link text and passage name as separate arguments, a link markup, or an image markup.
Note:
If you want to return to a previously visited passage, rather than undo a moment within the history, see the <<return>>
macro or the previous()
function.
v2.0.0
: Introduced.v2.37.0
: Added optional passage name argument in separate argument form.linkText
: (optional if passageName
is not specified) The text of the link. May contain markup.passageName
: (optional) The name of the moment to undo to until it's reached.linkMarkup
: The link markup to use (regular syntax only, no setters).imageMarkup
: The image markup to use (regular syntax only, no setters).Assume your story history consists of three moments:
A, B, [C]
N.b., the square brackets there denote the active moment.
Using <<back>>
once upon that history will change it to be thus:
A, [B], C
I.e., the history was rolled back to the previous moment.
→ Creates a link that undoes the most recent moment, with default text
<<back>>
→ Creates a link that undoes the most recent moment, with text "Home."
<<back "Home.">>
→ Creates a link that undoes past moments until the most recent "HQ" moment is reached, with text "Home."
<<back "Home." "HQ">>
→ Creates a link that undoes past moments until the most recent "HQ" moment is reached, with default text
<<back [[HQ]]>>
→ Creates a link that undoes past moments until the most recent "HQ" moment is reached, with text "Home."
<<back [[Home.|HQ]]>>
→ Creates a link that undoes the most recent moment, with image "home.png"
<<back [img[home.png]]>>
→ Creates a link that undoes past moments until the most recent "HQ" moment is reached, with image "home.png"
<<back [img[home.png][HQ]]>>
<<return [linkText [passageName]]>>
<<return linkMarkup>>
<<return imageMarkup>>
Creates a link that navigates forward to a previously visited passage. May be called with, optional, the link text and passage name as separate arguments, a link markup, or an image markup.
Note:
If you want to undo previous moments within the history, rather than return to a passage, see the <<back>>
macro.
v2.0.0
: Introduced.v2.37.0
: Added optional passage name argument in separate argument form.linkText
: (optional if passageName
is not specified) The text of the link. May contain markup.passageName
: (optional) The name of the passage to go to.linkMarkup
: The link markup to use (regular syntax only, no setters).imageMarkup
: The image markup to use (regular syntax only, no setters).Note:
The versions that forward to a specific passage are largely unnecessary, as you could simply use a normal link, and exist solely for compatibility with the <<back>>
macro.
Assume your story history consists of three moments:
A, B, [C]
N.b., the square brackets there denote the active moment.
Using <<return>>
once upon that history will change it to be thus:
A, B, C, [B]
I.e., a new moment, to the same passage as the previous moment, was added to the history.
→ Creates a link that forwards to the previous passage, with default text
<<return>>
→ Creates a link that forwards to the previous passage, with text "Home."
<<return "Home.">>
→ Creates a link that forwards to the "HQ" passage, with text "Home."
<<return "Home." "HQ">>
→ Creates a link that forwards to the "HQ" passage, with default text
<<return [[HQ]]>>
→ Creates a link that forwards to the "HQ" passage, with text "Home."
<<return [[Home.|HQ]]>>
→ Creates a link that forwards to the previous passage, with image "home.png"
<<return [img[home.png]]>>
→ Creates a link that forwards to the "HQ" passage, with image "home.png"
<<return [img[home.png][HQ]]>>
<<actions passageList>>
<<actions linkMarkupList>>
<<actions imageMarkupList>>
Deprecated: This macro has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.<<choice passageName [linkText]>>
<<choice linkMarkup>>
<<choice imageMarkup>>
Deprecated: This macro has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.
Warning:
All DOM macros require the elements to be manipulated to be on the page. As a consequence, you cannot use them directly within a passage to modify elements within said passage, since the elements they are targeting are still rendering, thus not yet on the page. You must, generally, use them with an interactive macro—e.g., <<link>>
macro—the <<done>>
macro, or within the PassageDone
special passage. Elements that are already part of the page, on the other hand, present no issues.
<<addclass selector classNames>>
Adds classes to the selected element(s).
See: DOM macro warning.
v2.0.0
: Introduced.selector
: The CSS/jQuery-style selector used to target element(s).classNames
: The names of the classes, separated by spaces.<<addclass "body" "day rain">> → Add the classes "day" and "rain" to the <body> element
<<addclass "#pie" "cherry">> → Add the class "cherry" to the element with the ID "pie"
<<addclass ".joe" "angry">> → Add the class "angry" to all elements containing the class "joe"
<<append selector [transition|t8n]>> … <</append>>
Executes its contents and appends the output to the contents of the selected element(s).
See: DOM macro warning.
v2.0.0
: Introduced.v2.25.0
: Added transition
and t8n
keywords.selector
: The CSS/jQuery-style selector used to target element(s).transition
: (optional) Keyword, used to signify that a CSS transition should be applied to the incoming insertions.t8n
: (optional) Keyword, alias for transition
.→ Example setup
I saw a <span id="dog">dog</span>.
→ Append to the contents of the target element
<<link "Doing">>
<<append "#dog">> chasing a cat<</append>>
<</link>>
→ Result, after clicking
I saw a <span id="dog">dog chasing a cat</span>.
→ Example setup
I saw a <span id="dog">dog</span>.
→ Append to the contents of the target element
<<link "Doing">>
<<append "#dog" t8n>> chasing a cat<</append>>
<</link>>
→ Result, after clicking
I saw a <span id="dog">dog<span class="macro-append-insert"> chasing a cat</span></span>.
<<copy selector>>
Outputs a copy of the contents of the selected element(s).
Warning:
Most interactive elements—e.g., passage links, interactive macros, etc.—cannot be properly copied via <<copy>>
. Attempting to do so will, usually, result in something that's non-functional.
See: DOM macro warning.
v2.0.0
: Introduced.selector
: The CSS/jQuery-style selector used to target element(s).→ Example setup
I'd like a <span id="snack-source">slice of Key lime pie</span>, please.
I'll have a <span id="snack-dest">breadstick</span>, thanks.
→ Replace the contents of the source target element with a copy of the destination target element
<<link "Have the same">>
<<replace "#snack-dest">><<copy "#snack-source">> too<</replace>>
<</link>>
→ Result, after the click
I'd like a <span id="snack-source">slice of Key lime pie</span>, please.
I'll have a <span id="snack-dest">slice of Key lime pie too</span>, thanks.
<<prepend selector [transition|t8n]>> … <</prepend>>
Executes its contents and prepends the output to the contents of the selected element(s).
See: DOM macro warning.
v2.0.0
: Introduced.v2.25.0
: Added transition
and t8n
keywords.selector
: The CSS/jQuery-style selector used to target element(s).transition
: (optional) Keyword, used to signify that a CSS transition should be applied to the incoming insertions.t8n
: (optional) Keyword, alias for transition
.→ Example setup
I saw a <span id="dog">dog</span>.
→ Prepend to the contents of the target element
<<link "Size">>
<<prepend "#dog">>big <</prepend>>
<</link>>
→ Result, after clicking
I saw a <span id="dog">big dog</span>.
→ Example setup
I saw a <span id="dog">dog</span>.
→ Prepend to the contents of the target element
<<link "Size">>
<<prepend "#dog" t8n>>big <</prepend>>
<</link>>
→ Result, after clicking
I saw a <span id="dog"><span class="macro-prepend-insert">big </span>dog</span>.
<<remove selector>>
Removes the selected element(s).
See: DOM macro warning.
Note:
If you simply want to empty the selected element(s), not remove them outright, you should use an empty <<replace>>
macro instead.
v2.0.0
: Introduced.selector
: The CSS/jQuery-style selector used to target element(s).→ Given the following
I'd like a <span id="huge-cupcake">humongous </span>cupcake, please.
→ Remove the target element
<<link "Go small">>
<<remove "#huge-cupcake">>
<</link>>
→ Result, after the click
I'd like a cupcake, please.
<<removeclass selector [classNames]>>
Removes classes from the selected element(s).
See: DOM macro warning.
v2.0.0
: Introduced.selector
: The CSS/jQuery-style selector used to target element(s).classNames
: (optional) The names of the classes, separated by spaces. If no class names are given, removes all classes.<<removeclass "body" "day rain">> → Remove the classes "day" and "rain" from the <body> element
<<removeclass "#pie" "cherry">> → Remove the class "cherry" from the element with the ID "pie"
<<removeclass ".joe" "angry">> → Remove the class "angry" from all elements containing the class "joe"
<<removeclass "#begone">> → Remove all classes from the element with the ID "begone"
<<replace selector [transition|t8n]>> … <</replace>>
Executes its contents and replaces the contents of the selected element(s) with the output.
See: DOM macro warning.
v2.0.0
: Introduced.v2.25.0
: Added transition
and t8n
keywords.selector
: The CSS/jQuery-style selector used to target element(s).transition
: (optional) Keyword, used to signify that a CSS transition should be applied to the incoming insertions.t8n
: (optional) Keyword, alias for transition
.→ Example setup
I saw a <span id="dog">dog</span>.
→ Replace the contents of the target element
<<link "Breed">>
<<replace "#dog">>Catahoula Cur<</replace>>
<</link>>
→ Result, after clicking
I saw a <span id="dog">Catahoula Cur</span>.
→ Example setup
I saw a <span id="dog">dog</span>.
→ Replace the contents of the target element
<<link "Breed">>
<<replace "#dog" t8n>>Catahoula Cur<</replace>>
<</link>>
→ Result, after clicking
I saw a <span id="dog"><span class="macro-replace-insert">Catahoula Cur</span></span>.
<<toggleclass selector classNames>>
Toggles classes on the selected element(s)—i.e., adding them if they don't exist, removing them if they do.
See: DOM macro warning.
v2.0.0
: Introduced.selector
: The CSS/jQuery-style selector used to target element(s).classNames
: The names of the classes, separated by spaces.<<toggleclass "body" "day rain">> → Toggle the classes "day" and "rain" on the <body> element
<<toggleclass "#pie" "cherry">> → Toggle the class "cherry" on the element with the ID "pie"
<<toggleclass ".joe" "angry">> → Toggle the class "angry" on all elements containing the class "joe"
Warning: The audio subsystem that supports the audio macros comes with some built-in limitations and it is strongly recommended that you familiarize yourself with them.
<<audio trackIdList actionList>>
Controls the playback of audio tracks, which must be set up via <<cacheaudio>>
.
See: Audio macro limitations.
Note:
The <<audio>>
macro cannot affect playlist tracks whose ownership has been transferred to their respective playlist. Meaning those set up via <<createplaylist>>
with its own
action, as owned playlist tracks are solely under the control of their playlist.
Note:
The Config.audio.pauseOnFadeToZero
setting (default: true
) controls whether tracks that have been faded to 0
volume (silent) are automatically paused.
v2.0.0
: Introduced.v2.1.0
: Added fadeoverto
action.v2.8.0
: Added group ID(s).v2.28.0
: Added load
and unload
actions.v2.37.0
: Added :stopped
predefined group ID.trackIdList
: The list of track and/or group IDs, separated by spaces. See below for details on group IDs.actionList
: The list of actions to perform. Available actions are:
fadein
: Start playback of the selected tracks and fade them from their current volume level to 1
(loudest) over 5
seconds.fadeout
: Start playback of the selected tracks and fade them from their current volume level to 0
(silent) over 5
seconds.fadeoverto
seconds
level
: Start playback of the selected tracks and fade them from their current volume level to the specified level over the specified number of seconds.fadeto
level
: Start playback of the selected tracks and fade them from their current volume level to the specified level over 5
seconds.goto
passage
: Forwards the player to the passage with the given name when playback of the first of the selected tracks ends normally. May be called either with the passage name or with a link markup.load
: Pause playback of the selected tracks and, if they're not already in the process of loading, force them to drop any existing data and begin loading. NOTE: This should not be done lightly if your audio sources are on the network, as it forces the player to begin downloading them.loop
: Set the selected tracks to repeat playback upon ending normally.mute
: Mute the volume of the selected tracks—effectively volume 0
, except without changing the volume level.pause
: Pause playback of the selected tracks.play
: Start playback of the selected tracks.stop
: Stop playback of the selected tracks.time
seconds
: Set the current playback time of the selected tracks to the specified number of seconds. Valid values are floating-point numbers in the range 0
(start) to the maximum duration—e.g., 60
is 60
is sixty seconds in, 90.5
is ninety-point-five seconds in.unload
: Stop playback of the selected tracks and force them to drop any existing data. NOTE: Once unloaded, playback cannot occur until a load
action is issued.unloop
: Set the selected tracks to not repeat playback (this is the default).unmute
: Unmute the volume of the selected tracks (this is the default).volume
level
: Set the volume of the selected tracks to the specified level. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.Group IDs allow several tracks to be selected simultaneously without needing to specify each one individually. There are several predefined group IDs (:all
, :looped
, :muted
, :paused
, :playing
, :stopped
) and custom IDs may be defined via <<createaudiogroup>>
. The :not()
group modifier syntax (groupId:not(trackIdList)
) allows a group to have some of its tracks excluded from selection.
→ Start playback of paused tracks
<<audio ":paused" play>>
→ Pause playback of playing tracks
<<audio ":playing" pause>>
→ Stop playback of playing tracks
<<audio ":playing" stop>>
→ Stop playback of all tracks
<<audio ":all" stop>>
→ Stop playback of playing tracks except those in the ":ui" group
<<audio ":playing:not(:ui)" stop>>
→ Change the volume of all tracks except those in the ":ui" group
→ to 40%, without changing the current playback state
<<audio ":all:not(:ui)" volume 0.40>>
→ Given the following (best done in the StoryInit special passage)
<<cacheaudio "bgm_space" "media/audio/space_quest.mp3" "media/audio/space_quest.ogg">>
→ Start playback
<<audio "bgm_space" play>>
→ Start playback at 50% volume
<<audio "bgm_space" volume 0.5 play>>
→ Start playback at 120 seconds in
<<audio "bgm_space" time 120 play>>
→ Start repeating playback
<<audio "bgm_space" loop play>>
→ Start playback and fade from 0% to 100% volume
<<audio "bgm_space" volume 0 fadein>>
→ Start playback and fade from 75% to 0% volume
<<audio "bgm_space" volume 0.75 fadeout>>
→ Start playback and fade from 25% to 75% volume
<<audio "bgm_space" volume 0.25 fadeto 0.75>>
→ Start playback and fade from 25% to 75% volume over 30 seconds
<<audio "bgm_space" volume 0.25 fadeoverto 30 0.75>>
→ Start playback and goto the "Peace Moon" passage upon ending normally
<<audio "bgm_space" play goto "Peace Moon">>
→ Pause playback
<<audio "bgm_space" pause>>
→ Stop playback
<<audio "bgm_space" stop>>
→ Mute playback, without changing the current playback state
<<audio "bgm_space" mute>>
→ Unmute playback, without changing the current playback state
<<audio "bgm_space" unmute>>
→ Change the volume to 40%, without changing the current playback state
<<audio "bgm_space" volume 0.40>>
→ Seek to 90 seconds in, without changing the current playback state
<<audio "bgm_space" time 90>>
load
and unload
actionsWarning: Be very careful with these if your audio sources are on the network, as you are forcing players to begin downloading them. Not everyone has blazing fast internet with unlimited data—especially true for mobile users. Pease, do not take your players' bandwidth and data usage lightly.
→ If it's not currently loading, drop existing data buffers and load the track
<<audio "bgm_space" load>>
→ Unload the track, dropping existing data buffers
<<audio "bgm_space" unload>>
<<cacheaudio trackId sourceList>>
Caches an audio track for use by the other audio macros.
Note:
The StoryInit
special passage is normally the best place to set up tracks.
v2.0.0
: Introduced.trackId
: The ID of the track, which will be used to reference it.sourceList
: A space separated list of sources for the track. Only one is required, though supplying additional sources in differing formats is recommended, as no single format is supported by all browsers. A source must be either a URL (absolute or relative) to an audio resource, the name of an audio passage, or a data URI. In rare cases where the audio format cannot be automatically detected from the source (URLs are parsed for a file extension, data URIs are parsed for the media type), a format specifier may be prepended to the front of each source to manually specify the format (syntax: formatId|
, where formatId
is the audio format—generally, whatever the file extension would normally be; e.g., mp3
, mp4
, ogg
, weba
, wav
).→ Cache a track with the ID "boom" and one source via relative URL
<<cacheaudio "boom" "media/audio/explosion.mp3">>
→ Cache a track with the ID "boom" and one source via audio passage
<<cacheaudio "boom" "explosion">>
→ Cache a track with the ID "bgm_space" and two sources via relative URLs
<<cacheaudio "bgm_space" "media/audio/space_quest.mp3" "media/audio/space_quest.ogg">>
→ Cache a track with the ID "what" and one source via URL with a format specifier
<<cacheaudio "what" "mp3|http://an-audio-service.com/a-user/a-track-id">>
<<createaudiogroup groupId>>
[<<track trackId>> …]
<</createaudiogroup>>
Collects tracks, which must be set up via <<cacheaudio>>
, into a group via its <<track>>
children. Groups are useful for applying actions to multiple tracks simultaneously and/or excluding the included tracks from a larger set when applying actions.
Note:
The StoryInit
special passage is normally the best place to set up groups.
v2.19.0
: Introduced.v2.37.0
: Added :stopped
predefined group ID.<<createaudiogroup>>
groupId
: The ID of the group that will be used to reference it and must begin with a colon. NOTE: There are several predefined group IDs (:all
, :looped
, :muted
, :paused
, :playing
, :stopped
) and the :not
group modifier that cannot be reused/overwritten.<<track>>
trackId
: The ID of the track.→ Given the following (best done in the StoryInit special passage)
<<cacheaudio "ui_beep" "media/audio/ui/beep.mp3">>
<<cacheaudio "ui_boop" "media/audio/ui/boop.mp3">>
<<cacheaudio "ui_swish" "media/audio/ui/swish.mp3">>
→ Set up a group ":ui" with the tracks: "ui_beep", "ui_boop", and "ui_swish"
<<createaudiogroup ":ui">>
<<track "ui_beep">>
<<track "ui_boop">>
<<track "ui_swish">>
<</createaudiogroup>>
<<createplaylist listId>>
[<<track trackId actionList>> …]
<</createplaylist>>
Collects tracks, which must be set up via <<cacheaudio>>
, into a playlist via its <<track>>
children.
Note:
The StoryInit
special passage is normally the best place to set up playlists.
v2.8.0
: Introduced.<<createplaylist>>
listId
: The ID of the playlist, which will be used to reference it.<<track>>
trackId
: The ID of the track.actionList
: The list of actions to perform. Available actions are:
volume
level
: (optional) Set the base volume of the track within the playlist to the specified level. If omitted, defaults to the track's current volume. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.own
: (optional) Keyword, used to signify that the playlist should create its own independent copy of the track, rather than simply referencing the existing version. Owned copies are solely under the control of their playlist—meaning <<audio>>
actions cannot affect them, even when using group IDs.→ Given the following setup (best done in the StoryInit special passage)
<<cacheaudio "swamped" "media/audio/Swamped.mp3">>
<<cacheaudio "heavens_a_lie" "media/audio/Heaven's_A_Lie.mp3">>
<<cacheaudio "closer" "media/audio/Closer.mp3">>
<<cacheaudio "to_the_edge" "media/audio/To_The_Edge.mp3">>
→ Create a playlist "bgm_lacuna" with the tracks: "swamped", "heavens_a_lie", "closer", and "to_the_edge"
<<createplaylist "bgm_lacuna">>
<<track "swamped" volume 1>> → Add "swamped" at 100% volume
<<track "heavens_a_lie" volume 0.5>> → Add "heavens_a_lie" at 50% volume
<<track "closer" own>> → Add an owned copy of "closer" at its current volume
<<track "to_the_edge" volume 1 own>> → Add an owned copy of "to_the_edge" at 100% volume
<</createplaylist>>
<<masteraudio actionList>>
Controls the master audio settings.
See: Audio macro limitations.
v2.8.0
: Introduced.v2.28.0
: Added load
, muteonhide
, nomuteonhide
, and unload
actions.actionList
: The list of actions to perform. Available actions are:
load
: Pause playback of all tracks and, if they're not already in the process of loading, force them to drop any existing data and begin loading. NOTE: This should not be done lightly if your audio sources are on the network, as it forces the player to begin downloading them.mute
: Mute the master volume (effectively volume 0
, except without changing the volume level).muteonhide
: Enable automatic muting of the master volume when losing visibility—i.e., when switched to another tab or the browser window is minimized.nomuteonhide
: Disable automatic muting of the master volume when losing visibility (this is the default).stop
: Stop playback of all tracks.unload
: Stop playback of all tracks and force them to drop any existing data. NOTE: Once unloaded, playback cannot occur until a load
action is issued for each track—either a master load
action, to affect all tracks, or an <<audio>>
/<<playlist>>
load
action, to affect only certain tracks.unmute
: Unmute the master volume (this is the default).volume
level
: Set the master volume to the specified level. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.→ Stop playback of all registered tracks, no exceptions
<<masteraudio stop>>
→ Change the master volume to 40%
<<masteraudio volume 0.40>>
→ Mute the master volume
<<masteraudio mute>>
→ Unmute the master volume
<<masteraudio unmute>>
→ Enable automatic muting of the master volume when losing visibility
<<masteraudio muteonhide>>
→ Disable automatic muting of the master volume when losing visibility
<<masteraudio nomuteonhide>>
load
and unload
actionsWarning: Be very careful with these if your audio sources are on the network, as you are forcing players to begin downloading them. Not everyone has blazing fast internet with unlimited data—especially true for mobile users. Pease, do not take your players' bandwidth and data usage lightly.
→ If they're not currently loading, drop existing data buffers and load all tracks
<<masteraudio load>>
→ Unload all tracks, dropping existing data buffers
<<masteraudio unload>>
<<playlist listId actionList>>
Controls the playback of the playlist, which must be set up via <<createplaylist>>
.
See: Audio macro limitations.
Note:
The Config.audio.pauseOnFadeToZero
setting (default: true
) controls whether tracks that have been faded to 0
volume (silent) are automatically paused.
v2.0.0
: Introduced, compatible with <<setplaylist>>
.v2.1.0
: Added fadeoverto
action.v2.8.0
: Added listId
argument, compatible with <<createplaylist>>
.v2.28.0
: Added load
and unload
actions.<<createplaylist>>
-compatible formlistId
: The ID of the playlist.actionList
: The list of actions to perform. Available actions are:
fadein
: Start playback of the playlist and fade the current track from its current volume level to 1
(loudest) over 5
seconds.fadeout
: Start playback of the playlist and fade the current track from its current volume level to 0
(silent) over 5
seconds.fadeoverto
seconds
level
: Start playback of the playlist and fade the current track from its current volume level to the specified level over the specified number of seconds.fadeto
level
: Start playback of the playlist and fade the current track from its current volume level to the specified level over 5
seconds.load
: Pause playback of the playlist and, if its tracks are not already in the process of loading, force them to drop any existing data and begin loading. NOTE: This should not be done lightly if your audio sources are on the network, as it forces the player to begin downloading them.loop
: Set the playlist to repeat playback upon ending.mute
: Mute the volume of the playlist (effectively volume 0
, except without changing the volume level).pause
: Pause playback of the playlist.play
: Start playback of the playlist.shuffle
: Set the playlist to randomly shuffle.skip
: Skip ahead to the next track in the queue. An empty queue will not be refilled unless repeat playback has been set.stop
: Stop playback of the playlist.unload
: Stop playback of the playlist and force its tracks to drop any existing data. NOTE: Once unloaded, playback cannot occur until a load
action is issued.unloop
: Set the playlist to not repeat playback (this is the default).unmute
: Unmute the volume of the playlist (this is the default).unshuffle
: Set the playlist to not randomly shuffle (this is the default).volume
level
: Set the volume of the playlist to the specified level. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.<<setplaylist>>
-compatible formactionList
: Identical to the <<createplaylist>>
-compatible form.<<createplaylist>>
-compatible form shown)→ Given the following (best done in the StoryInit special passage)
<<cacheaudio "swamped" "media/audio/Swamped.mp3">>
<<cacheaudio "heavens_a_lie" "media/audio/Heaven's_A_Lie.mp3">>
<<cacheaudio "closer" "media/audio/Closer.mp3">>
<<cacheaudio "to_the_edge" "media/audio/To_The_Edge.mp3">>
<<createplaylist "bgm_lacuna">>
<<track "swamped" volume 1>>
<<track "heavens_a_lie" volume 1>>
<<track "closer" volume 1>>
<<track "to_the_edge" volume 1>>
<</createplaylist>>
→ Start playback
<<playlist "bgm_lacuna" play>>
→ Start playback at 50% volume
<<playlist "bgm_lacuna" volume 0.5 play>>
→ Start non-repeating playback
<<playlist "bgm_lacuna" unloop play>>
→ Start playback with a randomly shuffled playlist
<<playlist "bgm_lacuna" shuffle play>>
→ Start playback and fade from 0% to 100% volume
<<playlist "bgm_lacuna" volume 0 fadein>>
→ Start playback and fade from 75% to 0% volume
<<playlist "bgm_lacuna" volume 0.75 fadeout>>
→ Start playback and fade from 25% to 75% volume
<<playlist "bgm_lacuna" volume 0.25 fadeto 0.75>>
→ Start playback and fade from 25% to 75% volume over 30 seconds
<<playlist "bgm_lacuna" volume 0.25 fadeoverto 30 0.75>>
→ Pause playback
<<playlist "bgm_lacuna" pause>>
→ Stop playback
<<playlist "bgm_lacuna" stop>>
→ Mute playback, without changing the current playback state
<<playlist "bgm_lacuna" mute>>
→ Unmute playback, without changing the current playback state
<<playlist "bgm_lacuna" unmute>>
→ Change the volume to 40%, without changing the current playback state
<<playlist "bgm_lacuna" volume 0.40>>
→ Set the playlist to randomly shuffle, without changing the current playback state
<<playlist "bgm_lacuna" shuffle>>
load
and unload
actionsWarning: Be very careful with these if your audio sources are on the network, as you are forcing players to begin downloading them. Not everyone has blazing fast internet with unlimited data—especially true for mobile users. Pease, do not take your players' bandwidth and data usage lightly.
→ If they're not currently loading, drop existing data buffers and load all of the playlist's tracks
<<playlist "bgm_lacuna" load>>
→ Unload all of the playlist's tracks, dropping existing data buffers
<<playlist "bgm_lacuna" unload>>
<<removeaudiogroup groupId>>
Removes the audio group with the given ID.
Note:
You may not remove the predefined group IDs (:all
, :looped
, :muted
, :paused
, :playing
, :stopped
) or the :not
group modifier.
v2.28.0
: Introduced.v2.37.0
: Added :stopped
predefined group ID.groupId
: The ID of the group.→ Given a group set up via <<createaudiogroup ":ui">>…<</createplaylist>>
<<removeaudiogroup ":ui">>
<<removeplaylist listId>>
Removes the playlist with the given ID.
v2.8.0
: Introduced.listId
: The ID of the playlist.→ Given a playlist set up via <<createplaylist "bgm_lacuna">>…<</createplaylist>>
<<removeplaylist "bgm_lacuna">>
<<waitforaudio>>
Displays the loading screen until all currently registered audio has either loaded to a playable state or aborted loading due to errors. Requires tracks to be set up via <<cacheaudio>>
.
Note:
This macro should be invoked once following any invocations of <<cacheaudio>>
and <<createplaylist>>
, if any <<track>>
definitions used the copy
keyword, for which you want the loading screen displayed.
v2.8.0
: Introduced.<<cacheaudio "a" "a_track.…">>
<<cacheaudio "b" "b_track.…">>
<<cacheaudio "c" "c_track.…">>
<<cacheaudio "d" "d_track.…">>
<<waitforaudio>>
→ First, register the tracks that will be needed soon
<<cacheaudio "a" "a_track.…">>
<<cacheaudio "b" "b_track.…">>
→ Next, load all currently registered tracks (meaning: "a" and "b")
<<waitforaudio>>
→ Finally, register any tracks that won't be needed until later
<<cacheaudio "c" "c_track.…">>
<<cacheaudio "d" "d_track.…">>
<<done>> … <</done>>
Silently executes its contents when the page is done rendering and the engine has become idle. Generally, only really useful for running code that needs to manipulate elements from the incoming passage, since you must wait until they've been added to the page.
Tip:
If you need to run the same code on multiple passages, consider using the PassageDone
special passage or, for a JavaScript/TwineScript solution, a :passagedisplay
event instead. They serve the same basic purpose as the <<done>>
macro, but are run each time passage navigation occurs.
v2.35.0
: Introduced.v2.36.0
: Changed delay mechanism to improve waiting on the DOM.@@#spy;@@
<<done>>
<<replace "#spy">>I spy with my little eye, a crab rangoon.<</replace>>
<</done>>
<<goto passageName>>
<<goto linkMarkup>>
Immediately forwards the player to the passage with the given name. May be called either with the passage name or with a link markup.
Note:
In most cases, you will not need to use <<goto>>
as there are often better and easier ways to forward the player. For example, a common use of <<link>>
is to perform various actions before forwarding the player to another passage. In that case, unless you need to dynamically determine the destination passage within the <<link>>
body, <<goto>>
is unnecessary as <<link>>
already includes the ability to forward the player.
Warning:
Using <<goto>>
to automatically forward players from one passage to another with no input from them will both create junk moments within the story history and make it extremely difficult for players to navigate the history. It is strongly recommended that you look into other methods to achieve your goals instead—e.g., Config.navigation.override
.
Warning:
<<goto>>
does not terminate passage rendering in the passage where it was encountered, so care must be taken to ensure that no unwanted state modifications occur after its call.
v2.0.0
: Introduced.passageName
: The name of the passage to go to.linkMarkup
: The link markup to use (regular syntax only, no setters).→ Passage name form
<<goto "Somewhere over yonder">>
<<goto $selectedPassage>>
→ Link markup form
<<goto [[Somewhere over yonder]]>>
<<goto [[$selectedPassage]]>>
<<repeat delay [transition|t8n]>> … <</repeat>>
Repeatedly executes its contents after the given delay, inserting any output into the passage in its place. May be terminated by a <<stop>>
macro.
Note: Passage navigation terminates all pending timed executions.
v2.0.0
: Introduced.delay
: The amount of time to delay, as a valid CSS time value—e.g., 5s
and 500ms
. The minimum delay is 40ms
.transition
: (optional) Keyword, used to signify that a CSS transition should be applied to the incoming insertions.t8n
: (optional) Keyword, alias for transition
.→ A countdown timer
<<set $seconds to 10>>\
Countdown: <span id="countdown">$seconds seconds remaining</span>!\
<<silent>>
<<repeat 1s>>
<<set $seconds to $seconds - 1>>
<<if $seconds gt 0>>
<<replace "#countdown">>$seconds seconds remaining<</replace>>
<<else>>
<<replace "#countdown">>Too Late<</replace>>
/* do something useful here */
<<stop>>
<</if>>
<</repeat>>
<</silent>>
<<stop>>
Used within <<repeat>>
macros. Terminates the execution of the current <<repeat>>
.
v2.0.0
: Introduced.<<timed delay [transition|t8n]>> …
[<<next [delay]>> …]
<</timed>>
Executes its contents after the given delay, inserting any output into the passage in its place. Additional timed executions may be chained via <<next>>
.
Note: Passage navigation terminates all pending timed executions.
v2.0.0
: Introduced.<<timed>>
delay
: The amount of time to delay, as a valid CSS time value—e.g., 5s
and 500ms
. The minimum delay is 40ms
.transition
: (optional) Keyword, used to signify that a CSS transition should be applied to the incoming insertions.t8n
: (optional) Keyword, alias for transition
.<<next>>
delay
: (optional) The amount of time to delay, as a valid CSS time value—e.g., 5s
and 500ms
. The minimum delay is 40ms
. If omitted, the last delay specified, from a <<next>>
or the parent <<timed>>
, will be used.→ Insert some text after 5 seconds with a transition
I want to go to…<<timed 5s t8n>> WONDERLAND!<</timed>>
→ Replace some text after 10 seconds
I like green <span id="eggs">eggs</span> and ham!\
<<timed 10s>><<replace "#eggs">>pancakes<</replace>><</timed>>
→ A execute <<goto>> after 10 seconds
<<timed 10s>><<goto "To the Moon, Alice">><</timed>>
→ Insert some text in 2 second intervals three times (at: 2s, 4s, 6s)
<<timed 2s>>Hi! Ho!
<<next>>Hi! Ho!
<<next>>It's off to work we go!
<</timed>>
→ Set a $variable after 4 seconds, 3 seconds, 2 seconds, and 1 second
<<silent>>
<<set $choice to 0>>
<<timed 4s>>
<<set $choice to 1>>
<<next 3s>>
<<set $choice to 2>>
<<next 2s>>
<<set $choice to 3>>
<<next 1s>>
<<set $choice to 4>>
<</timed>>
<</silent>>
→ Replace some text with a variable interval
→ Given: _delay is "2s" the interval will be 2 seconds
I'll have <span id="drink">some water</span>, please.\
<<timed _delay>><<replace "#drink">>a glass of milk<</replace>>\
<<next>><<replace "#drink">>a can of soda<</replace>>\
<<next>><<replace "#drink">>a cup of coffee<</replace>>\
<<next>><<replace "#drink">>tea, southern style, sweet<</replace>>\
<<next>><<replace "#drink">>a scotch, neat<</replace>>\
<<next>><<replace "#drink">>a bottle of your finest absinthe<</replace>>\
<</timed>>
<<widget widgetName [container]>> … <</widget>>
Creates a new widget macro (henceforth, widget) with the given name. Widgets allow you to create macros by using the standard macros and markup that you use normally within your story. All widgets may access arguments passed to them via the _args
special variable. Block widgets may access the contents they enclose via the _contents
special variable.
Warning:
Widgets should always be defined within a widget
-tagged passage—any widgets that are not may be lost on page reload—and you may use as few or as many such passages as you desire. Do not add a widget
tag to any of the specially named passages and attempt to define your widgets there.
Warning:
The array-like object stored in the _args
variable should be treated as though it were immutable—i.e., unable to be modified—because in the future it will be made thus, so any attempt to modify it will cause an error.
v2.0.0
: Introduced.v2.36.0
: Added the container
keyword, _args
variable, and _contents
variable. Deprecated the $args
variable in favor of _args
.v2.37.0
: Added the _args.name
property.widgetName
: The name of the created widget, which should not contain whitespace or angle brackets (<
, >
). If the name of an existing widget is chosen, the new widget will overwrite the older version. NOTE: The names of existing macros are invalid widget names and any attempts to use such a name will cause an error.container
: (optional) Keyword, used to signify that the widget should be created as a container widget—i.e., non-void, requiring a closing tag; e.g., <<foo>>…<</foo>>
._args
& _contents
:The _args
special variable is used internally to store arguments passed to the widget—as zero-based indices; i.e., _args[0]
is the first parsed argument, _args[1]
is the second, etc—the full argument string in raw and parsed forms—accessed via the _args.raw
and _args.full
properties—and the widgets' name via the _args.name
property.
The _contents
special variable is used internally, by container widgets, to store the contents they enclose.
When a widget is called, any existing _args
variable, and for container widgets _contents
, is stored for the duration of the call and restored after. This means that non-widget uses of these special variable are completely safe, though this does have the effect that uses external to widgets are inaccessible within them unless passed in as arguments.
When calling one container widget directly from within another container widget, the _contents
special variable of the outer widget must not be included within the body of the call of the inner widget. Doing so will cause uncontrolled recursion. E.g.,
<<widget "inner" container>>
_contents
<</widget>>
<<widget "outer" container>>
<<inner>>_contents<</inner>>
<</widget>>
<<outer>>ford<</outer>>
Warning:
Unless localized by use of the <<capture>>
macro, any story or other temporary variables used within widgets are part of a story's normal variable store, so care must be taken not to accidentally either overwrite or pick up an existing value.
Note:
No line-break control mechanisms are used in the following examples for readability. In practice, you'll probably want to use either line continuations or one of the no-break methods: Config.passages.nobr
setting, nobr
special tag, <<nobr>>
macro.
→ Creating a gender pronoun widget
<<widget "he">>
<<if $pcSex eq "male">>
he
<<elseif $pcSex eq "female">>
she
<<else>>
it
<</if>>
<</widget>>
→ Using it
"Are you sure that <<he>> can be trusted?"
→ Creating a silly print widget
<<widget "pm">>
<<if _args[0]>>
<<print _args[0]>>
<<else>>
Mum's the word!
<</if>>
<</widget>>
→ Using it
<<pm>> → Outputs: Mum's the word!
<<pm "Hi!">> → Outputs: Hi!
→ Creating a simple dialog box widget
<<widget "say" container>>
<div class="say-box">
<img class="say-image" @src="'images/' + _args[0].toLowerCase() + '.png'">
<p class="say-text">_contents</p>
</div>
<</widget>>
→ Using it
<<say "Chapel">>Tweego is a pathway to many abilities some consider to be… unnatural.<</say>>
clone(original)
→ any
Returns a deep copy of the given value.
Only primitives, generic objects, Array
, Date
, Map
, RegExp
, and Set
are supported by default. Unsupported object types, either native or custom, will need to implement a .clone()
method to be properly supported by the clone()
function—when called on such an object, it will defer to the local method; see the Non-generic object types (classes) guide for more information.
Warning: Referential relationships between objects are not maintained—i.e., after cloning multiple references to an object will refer to seperate yet equivalent objects, as each reference receives its own clone of the original.
Warning: Generic objects have only their own enumerable properties copied. Non-enumerable properties and property descriptors are not duplicated. In particular, this means that getters/setters are not properly duplicated. If you need getters/setters, then you'll need to use a non-generic object/class.
v2.0.0
: Introduced.original
: (any
) The value to clone.A deep copy (any
) of the original value.
/* Given the following: */
<<set $foo to { id : 1 }>>
/* Without clone() */
<<set $bar to $foo>>
<<set $bar.id to 5>>
<<= $foo.id>> // Prints 5
<<= $bar.id>> // Prints 5
/* With clone() */
<<set $bar to clone($foo)>>
<<set $bar.id to 5>>
<<= $foo.id>> // Prints 1
<<= $bar.id>> // Prints 5
// Given the following:
let foo = { id : 1 };
// Without clone()
let bar = foo;
bar.id = 5;
foo.id; // Yields 5
bar.id; // Yields 5
// With clone()
let bar = clone(foo);
bar.id = 5;
foo.id; // Yields 1
bar.id; // Yields 5
either(list…)
→ any
Returns a random value from its given arguments.
v2.0.0
: Introduced.list
: (any
) The list of values to operate on. May be any combination of singular values, actual arrays, or array-like objects. All values will be concatenated into a single list for selection. NOTE: Does not flatten nested arrays—if this is required, the <Array>.flat()
method may be used to flatten the nested arrays prior to passing them to either()
.A random value (any
) from its given arguments.
Using singular values.
/* Returns a random pie from the whole list */
<<set $pie to either('Blueberry', 'Cherry', 'Pecan')>>
Using arrays.
/* Returns a random pie from the whole array */
<<set $pies to ['Blueberry', 'Cherry', 'Pecan']>>
<<set $pie to either($pies)>>
Using singular values and arrays.
/* Returns a random value from the whole list—i.e., 'A', 'B', 'C', 'D' */
<<set $letters to ['A', 'B']>>
<<set $letter to either($letters, 'C', 'D')>>
Using multiple arrays.
/* Returns a random value from the whole list—i.e., 'A', 'B', '1', '2' */
<<set $letters to ['A', 'B']>>
<<set $numerals to ['1', '2']>>
<<set $alphaNum to either($letters, $numerals)>>
Using singular values.
// Returns a random pie from the whole list
let pie = either('Blueberry', 'Cherry', 'Pecan');
Using arrays.
// Returns a random pie from the whole array
let pies = ['Blueberry', 'Cherry', 'Pecan'];
let pie = either(pies);
Using singular values and arrays.
// Returns a random value from the whole list—i.e., 'A', 'B', 'C', 'D'
let letters = ['A', 'B'];
let letter = either(letters, 'C', 'D');
Using multiple arrays.
// Returns a random value from the whole list—i.e., 'A', 'B', '1', '2'
let letters = ['A', 'B'];
let numerals = ['1', '2']
let alphaNum = either(letters, numerals);
forget(key)
Removes the specified key, and its associated value, from the story metadata store.
See Also:
memorize()
, recall()
.
v2.29.0
: Introduced.key
: (string
) The key to remove.An Error
or TypeError
instance.
/* Clears 'achievements' from the metadata store. */
<<run forget('achievements')>>
// Clears 'achievements' from the metadata store.
forget('achievements');
hasVisited(passageNames…)
→ boolean
Returns whether the passage with the given name occurred within the story history. If multiple passage names are given, returns the logical-AND aggregate of the set—i.e., true
if all were found, false
if any were not found.
v2.7.0
: Introduced.passageNames
: (string
| Array<string>
) The name(s) of the passage(s) to search for. May be a list or an array of passage names.Boolean true
if all were found, elsewise false
.
An Error
instance.
<<if hasVisited('Bar')>>
…has been to the Bar…
<</if>>
<<if not hasVisited('Bar')>>
…has never been to the Bar…
<</if>>
<<if hasVisited('Bar', 'Café')>>
…has been to both the Bar and Café…
<</if>>
<<if not hasVisited('Bar', 'Café')>>
…has never been to either the Bar, Café, or both…
<</if>>
if (hasVisited('Bar')) {
// Has been to the Bar.
}
if (!hasVisited('Bar')) {
// Has never been to the Bar.
}
if (hasVisited('Bar', 'Café')) {
// Has been to both the Bar and Café.
}
if (!hasVisited('Bar', 'Café')) {
// Has never been to either the Bar, Café, or both.
}
lastVisited(passageNames…)
→ integer number
Returns the number of turns that have passed since the last instance of the passage with the given name occurred within the story history or -1
, if it does not exist. If multiple passage names are given, returns the lowest count among them, which can be -1
.
v2.0.0
: Introduced.passageNames…
: (string
| Array<string>
) The name(s) of the passage(s) to search for. May be a list or an array of passage names.The lowest count (integer number
), elsewise -1
.
An Error
instance.
<<if lastVisited('Bar') is -1>>
…has never been to the Bar…
<</if>>
<<if lastVisited('Bar') is 0>>
…is currently in the Bar…
<</if>>
<<if lastVisited('Bar') is 1>>
…was in the Bar one turn ago…
<</if>>
<<if lastVisited('Bar', 'Café') is -1>>
…has never been to the Bar, Café, or both…
<</if>>
<<if lastVisited('Bar', 'Café') is 2>>
…has been to both the Bar and Café, most recently two turns ago…
<</if>>
if (lastVisited('Bar') === -1) {
// Has never been to the Bar.
}
if (lastVisited('Bar') === 0) {
// Is currently in the Bar.
}
if (lastVisited('Bar') === 1) {
// Was in the Bar one turn ago.
}
if (lastVisited('Bar', 'Café') === -1) {
// Has never been to the Bar, Café, or both.
}
if (lastVisited('Bar', 'Café') === 2) {
// Has been to both the Bar and Café, most recently two turns ago.
}
importScripts(urls…)
→ Promise
Load and integrate external JavaScript scripts.
Note:
Loading is done asynchronously at run time, so if the script must be available within a tight time frame, then you should use the Promise
returned by the function to ensure that the script is loaded before it is needed.
Note:
Your project's JavaScript section (Twine 2: the Story JavaScript; Twine 1/Twee: a script
-tagged passage) is normally the best place to call importScripts()
.
v2.16.0
: Introduced.urls
: (string
| Array<string>
) The URLs of the external scripts to import. Loose URLs are imported concurrently, arrays of URLs are imported sequentially.A Promise
that simply resolves, or rejects with an error if the script could not be loaded.
An Error
or TypeError
instance.
// Import all scripts concurrently
importScripts(
'https://somesite/a/path/a.js',
'https://somesite/a/path/b.js',
'https://somesite/a/path/c.js',
'https://somesite/a/path/d.js'
);
// Import all scripts sequentially
importScripts([
'https://somesite/a/path/a.js',
'https://somesite/a/path/b.js',
'https://somesite/a/path/c.js',
'https://somesite/a/path/d.js'
]);
// Import scripts a.js, b.js, and the c.js/d.js group concurrently,
// while importing c.js and d.js sequentially relative to each other
importScripts(
'https://somesite/a/path/a.js',
'https://somesite/a/path/b.js',
[
'https://somesite/a/path/c.js',
'https://somesite/a/path/d.js'
]
);
Promise
object// Import a script while using the returned Promise to ensure that
// the script has been fully loaded before executing dependent code
importScripts('https://somesite/a/path/a.js')
.then(() => {
// Code that depends on the script goes here
})
.catch((err) => {
// There was an error loading the script, log it to the console
console.log(err);
});
Promise
object for later use// Import a script while saving the returned Promise so it may be used later
setup.aScriptImport = importScripts('https://somesite/a/path/aScript.js');
// Use the returned Promise later on to ensure that the script has been fully
// loaded before executing dependent code
setup.aScriptImport
.then(() => {
// Code that depends on the script goes here
})
.catch((err) => {
// There was an error loading the script, log it to the console
console.log(err);
});
importStyles(urls…)
→ Promise
Load and integrate external CSS stylesheets.
Note:
Loading is done asynchronously at run time, so if the stylesheet must be available within a tight time frame, then you should use the Promise
returned by the function to ensure that the stylesheet is loaded before it is needed.
Note:
Your project's JavaScript section (Twine 2: the Story JavaScript; Twine 1/Twee: a script
-tagged passage) is normally the best place to call importStyles()
.
v2.16.0
: Introduced.urls
: (string
| Array<string>
) The URLs of the external stylesheets to import. Loose URLs are imported concurrently, arrays of URLs are imported sequentially.A Promise
that simply resolves, or rejects with an error if the style could not be loaded.
An Error
or TypeError
instance.
// Import all stylesheets concurrently
importStyles(
'https://somesite/a/path/a.css',
'https://somesite/a/path/b.css',
'https://somesite/a/path/c.css',
'https://somesite/a/path/d.css'
);
// Import all stylesheets sequentially
importStyles([
'https://somesite/a/path/a.css',
'https://somesite/a/path/b.css',
'https://somesite/a/path/c.css',
'https://somesite/a/path/d.css'
]);
// Import stylesheets a.css, b.css, and the c.css/d.css group concurrently,
// while importing c.css and d.css sequentially relative to each other
importStyles(
'https://somesite/a/path/a.css',
'https://somesite/a/path/b.css',
[
'https://somesite/a/path/c.css',
'https://somesite/a/path/d.css'
]
);
Promise
object// Grab a loading screen lock
var lsLockId = LoadScreen.lock();
// Import a stylesheet while using the returned Promise to ensure that the
// stylesheet has been fully loaded before unlocking the loading screen
importStyles('https://somesite/a/path/a.css')
.then(() => {
// The stylesheet has been loaded, release the loading screen lock
LoadScreen.unlock(lsLockId);
})
.catch((err) => {
// There was an error loading the stylesheet, log it to the console
console.log(err);
});
memorize(key, value)
Sets the specified key and value within the story metadata store, which causes them to persist over story and browser restarts. To update the value associated with a key, simply set it again.
Note: The story metadata, like saves, is tied to the specific story it was generated with. It is not a mechanism for moving data between stories.
Warning: The story metadata store is not, and should not be used as, a replacement for saves. Examples of good uses: achievement tracking, new game+ data, playthrough statistics, etc.
Warning: This feature is largely incompatible with private browsing modes, which cause all in-browser storage mechanisms to either persist only for the lifetime of the browsing session or fail outright.
v2.29.0
: Introduced.key
: (string
) The key that should be set.value
: (any
) The value to set.An TypeError
instance.
/* Sets 'achievements', with the given value, in the metadata store. */
<<run memorize('achievements', { ateYellowSnow : true })>>
/* Sets 'ngplus', with the given value, in the metadata store. */
<<run memorize('ngplus', true)>>
// Sets 'achievements', with the given value, in the metadata store.
memorize('achievements', { ateYellowSnow : true });
// Sets 'ngplus', with the given value, in the metadata store.
memorize('ngplus', true);
passage()
→ string
Returns the name of the active (present) passage.
v2.0.0
: Introduced.The name (string
) of the passage.
/* Link markup.*/
[[Reload passage|passage()]]
/* Link macro.*/
<<link "Reload passage" `passage()`>><</link>>
/* Get the name of the active passage. */
<<set $passageName to passage()>>
<<if passage() is 'Café'>>
…the active passage is the Café passage…
<</if>>
// Get the name of the active passage.
let passageName = passage();
if (passage() === 'Café') {
// The active passage is the Café passage.
}
previous()
→ string
Returns the name of the most recent previous passage whose name does not match that of the active passage or an empty string, if there is no such passage.
Warning:
If you need to go back multiple passages—e.g., if you have a menu and you want the player to return from any depth—then previous()
may be insufficient for your needs. In that case, you'll want to look at the arbitrarily long return.
v2.0.0
: Introduced.The name (string
) of the passage, elsewise an empty string (''
).
/* Link markup.*/
[[Return|previous()]]
/* Link macro.*/
<<link "Return" `previous()`>><</link>>
/* Get the name of the most recent non-active passage. */
<<set $previousName to previous()>>
<<if previous() is 'Café'>>
…the most recent non-active passage is the Café passage…
<</if>>
// Get the name of the most recent non-active passage.
let previousName = previous();
if (previous() === 'Café') {
// The most recent non-active passage is the Café passage.
}
random([min ,] max)
→ integer number
Returns a pseudo-random whole number (integer) within the range of the given bounds (inclusive)—i.e., [min, max].
Note:
By default, it returns non-deterministic results from Math.random()
, however, when the seedable PRNG has been enabled, via State.prng.init()
, it returns deterministic results from the seeded PRNG instead.
v2.0.0
: Introduced.min
: (optional, integer number
) The lower bound of the random number (inclusive). If omitted, defaults to 0
.max
: (integer number
) The upper bound of the random number (inclusive).A random whole number (integer number
).
An Error
or TypeError
instance.
/* Returns a number in the range 0–5 */
<<set $randInt to random(5)>>
/* Returns a number in the range 1–6 */
<<set _randInt to random(1, 6)>>
// Returns a number in the range 0–5
let randInt = random(5);
// Returns a number in the range 1–6
let randInt = random(1, 6);
randomFloat([min ,] max)
→ decimal number
Returns a pseudo-random decimal number (floating-point) within the range of the given bounds (inclusive for the minimum, exclusive for the maximum)—i.e., [min, max).
Note:
By default, it returns non-deterministic results from Math.random()
, however, when the seedable PRNG has been enabled, via State.prng.init()
, it returns deterministic results from the seeded PRNG instead.
v2.0.0
: Introduced.min
: (optional, decimal number
) The lower bound of the random number (inclusive). If omitted, defaults to 0.0
.max
: (decimal number
) The upper bound of the random number (exclusive).A random floating-point number (decimal number
).
An Error
or TypeError
instance.
/* Returns a number in the range 0.0–4.9999999… */
<<set $randNum to randomFloat(5.0)>>
/* Returns a number in the range 1.0–5.9999999… */
<<set _randNum to randomFloat(1.0, 6.0)>>
// Returns a number in the range 0.0–4.9999999…
let randNum = randomFloat(5.0);
// Returns a number in the range 1.0–5.9999999…
let randNum = randomFloat(1.0, 6.0);
recall(key [, defaultValue])
→ any
Returns the value associated with the specified key from the story metadata store or, if no such key exists, the specified default value, if any.
See Also:
forget()
, memorize()
.
v2.29.0
: Introduced.key
: (string
) The key whose value should be returned.defaultValue
: (optional, any
) The value to return if the key doesn't exist.A value (any
) from the specified key, elsewise the default value if specified.
A TypeError
instance.
/*
Set setup.achievements to the 'achievements' metadata, defaulting
to an empty generic object if no metadata exists.
*/
<<set setup.achievements to recall('achievements', {})>>
/* Set setup.ngplus to the 'ngplus' metadata, with no default. */
<<set setup.ngplus to recall('ngplus')>>
// Set setup.achievements to the 'achievements' metadata, defaulting
// to an empty generic object if no metadata exists.
setup.achievements = recall('achievements', {});
// Set setup.ngplus to the 'ngplus' metadata, with no default.
setup.ngplus = recall('ngplus');
setPageElement(idOrElement , passageNames [, defaultText])
→ HTMLElement
| null
Renders the selected passage into the target element, replacing any existing content, and returns the element. If no passages are found and default text is specified, it will be used instead.
v2.0.0
: Introduced.idOrElement
: (string
| HTMLElement
) The ID of the element or the element itself.passageNames
: (string
| Array<string>
) The name(s) of the passage(s) to search for. May be a single passage name or an array of passage names. If an array of passage names is specified, the first passage to be found is used.defaultText
: (optional, string
) The default text to use if no passages are found.An HTMLElement
instance, elsewise null
.
Note: As it is highly unlikely that either an array of passage names or default text will be needed in the vast majority of cases, only a few basic examples will be given.
/* Using an ID; given an existing element on the page: <div id="my-display"></div> */
<<run setPageElement('my-display', 'MyPassage')>>
/* Using an element; given a reference to an existing element: myElement */
<<run setPageElement(myElement, 'MyPassage')>>
// Using an ID; given an existing element on the page: <div id="my-display"></div>
setPageElement('my-display', 'MyPassage');
// Using an element; given a reference to an existing element: myElement
setPageElement(myElement, 'MyPassage');
tags([passageNames])
→ Array<string>
Returns a new array consisting of all of the tags of the given passage names.
v2.0.0
: Introduced.passageNames
: (optional, string
| Array<string>
) The passage names from which to collect tags. May be a list or an array of passage names. If omitted, will default to the active (present) passage—included passages do not count for this purpose; e.g., passages pulled in via <<include>>
, PassageHeader
, etc.An Array<string>
containing the tags.
/* Get the tags of the active passage. */
<<set $activeTags to tags()>>
/* Get the tags of the given passage. */
<<set $lonelyGladeTags to tags('Lonely Glade')>>
<<if tags().includes('forest')>>
…the active passage is part of the forest…
<</if>>
<<if tags('Lonely Glade').includes('forest')>>
…the Lonely Glade passage is part of the forest…
<</if>>
// Get the tags of the active passage.
let activeTags = tags();
// Get the tags of the given passage.
let lonelyGladeTags = tags('Lonely Glade');
if (tags().includes('forest')) {
// The active passage is part of the forest.
}
if (tags('Lonely Glade').includes('forest')) {
// The Lonely Glade passage is part of the forest.
}
temporary()
→ Object
Returns a reference to the current temporary variables store (equivalent to: State.temporary
). This is only really useful within pure JavaScript code, as within TwineScript you may simply access temporary variables natively.
v2.19.0
: Introduced.A reference to the temporary variable store (Object
).
// Given the following: _selection is 'Zagnut Bar'
if (temporary().selection === 'Zagnut Bar') {
// Do something…
}
time()
→ integer number
Returns the number of milliseconds that have passed since the current passage was rendered to the page.
v2.0.0
: Introduced.The milliseconds (integer number
) since the passage was rendered.
/* Links that vary based on the time. */
In the darkness, something wicked this way comes. Quickly! Do you \
<<link "try to run back into the light">>
<<if time() lt 10000>>
/* The player clicked the link in under 10s, so they escape. */
<<goto "Well lit passageway">>
<<else>>
/* Elsewise, they're eaten by a grue. */
<<goto "Eaten by a grue">>
<</if>>
<</link>> \
or [[stand your ground|Eaten by a grue]]?
triggerEvent(name [, targets [, options]])
Dispatches a synthetic event with the given name, optionally on the given targets and with the given options.
v2.37.0
: Introduced.Tip: If dispatching custom events, it is recommended that you limit your custom event names to the following characters: letters, digits, periods (.), hyphens (-), underscores (_), and colons (:).
name
: (string
) The name of the event to trigger. Both native and custom events are supported.targets
: (optional, Document
| HTMLElement
| jQuery
| NodeList
| Array<HTMLElement>
) The target(s) to trigger the event on. If omitted, will default to document
.options
: (optional, Object
) The options to be used when dispatching the event. See below for details.Warning:
Adding additional properties directly to event options objects is not recommended. Instead, use the detail
property.
An event options object should have some of the following properties:
bubbles
: (optional, boolean
) Whether the event bubbles (default: true
).cancelable
: (optional, boolean
) Whether the event is cancelable (default: true
).composed
: (optional, boolean
) Whether the event triggers listeners outside of a shadow root (default: false
).detail
: (optional, any
) Custom data sent with the event (default: undefined
). Although any type is allowable, an object is often the most practical.Note:
The macro examples would be exactly the same as the JavaScript examples, just wrapped in a <<script>>
macro.
Dispatch a custom fnord
event on document
.
triggerEvent('fnord');
Dispatch a click
event on the element bearing the ID some-menu
.
triggerEvent('click', document.getElementById('some-menu'));
Dispatch a custom update-meter
event on document
while specifying some options.
triggerEvent('update-meter', document, {
detail : {
tags : ['health', 'magick']
}
});
Various ways to dispatch a mouseover
event on all elements bearing the class flippable
.
triggerEvent('mouseover', document.getElementsByClassName('flippable'));
triggerEvent('mouseover', document.querySelectorAll('.flippable'));
triggerEvent('mouseover', jQuery('.flippable'));
turns()
→ integer number
Returns the total number (count) of played turns currently in effect—i.e., the number of played moments up to the present moment; future (rewound/undone) moments are not included within the total.
v2.0.0
: Introduced.The turn count (integer number
).
/* Record the turn count. */
<<set $turnCount to turns()>>
<<= 'This is turn #' + turns()>>
// Record the turn count.
let turnCount = turns();
variables()
→ Object
Returns a reference to the active (present) story variables store (equivalent to: State.variables
). This is only really useful within pure JavaScript code, as within TwineScript you may simply access story variables natively.
v2.0.0
: Introduced.A reference to the story variable store (Object
).
// Given: $hasGoldenKey is true
if (variables().hasGoldenKey) {
/* Do something… */
}
visited([passageNames])
→ integer number
Returns the number of times that the passage with the given name occurred within the story history. If multiple passage names are given, returns the lowest count among them.
v2.0.0
: Introduced.passageNames
: (optional, string
| Array<string>
) The name(s) of the passage(s) to search for. May be a list or an array of passage names. If omitted, will default to the current passage.The passage count (integer number
).
<<if visited() is 3>>
…has been to the current passage exactly three times…
<</if>>
<<if visited('Bar')>>
…has been to the Bar at least once…
<</if>>
<<if visited('Café') is 2>>
…has been to the Café exactly twice…
<</if>>
<<if visited('Bar', 'Café') is 4>>
…has been to both the Bar and Café four or more times…
<</if>>
if (visited() === 3) {
// Has been to the current passage exactly three times.
}
if (visited('Bar')) {
// Has been to the Bar at least once.
}
if (visited('Café') === 2) {
// Has been to the Café exactly twice.
}
if (visited('Bar', 'Café') === 4) {
// Has been to both the Bar and Café four or more times.
}
visitedTags(tags…)
→ integer number
Returns the number of passages within the story history that are tagged with all of the given tags.
v2.0.0
: Introduced.tags
: (string
| Array<string>
) The tags to search for. May be a list or an array of tags.The number (integer number
) of passages that are tagged with the given tags.
An Error
instance.
<<if visitedTags('forest')>>
…has been to some part of the forest at least once…
<</if>>
<<if visitedTags('forest', 'haunted') is 2>>
…has been to the haunted part of the forest exactly twice…
<</if>>
<<if visitedTags('forest', 'burned') gte 3>>
…has been to the burned part of the forest three or more times…
<</if>>
if (visitedTags('forest')) {
// Has been to some part of the forest at least once.
}
if (visitedTags('forest', 'haunted') === 2) {
// Has been to the haunted part of the forest exactly twice.
}
if (visitedTags('forest', 'burned') >= 3) {
// Has been to the burned part of the forest three or more times.
}
Most of the methods listed below are SugarCube extensions, with the rest being either JavaScript built-ins or bundled library methods that are listed here for their utility—though, this is not an exhaustive list.
For more information see:
<Array>.concat(members…)
→ Array<any>
Concatenates one or more members to the end of the base array and returns the result as a new array. Does not modify the original.
members
: (any
…) The members to concatenate. Members that are arrays will be merged—i.e., their members will be concatenated, rather than the array itself.A new Array
formed from concatenating all array members in order.
/* Given the following: */
<<set $fruits1 to ['Apples', 'Oranges']>>
<<set $fruits2 to ['Pears', 'Plums']>>
<<set $result to $fruits1.concat($fruits2)>>
/* Returns ['Apples', 'Oranges', 'Pears', 'Plums'] */
<<set $result to $fruits1.concat($fruits2, $fruits2)>>
/* Returns ['Apples', 'Oranges', 'Pears', 'Plums', 'Pears', 'Plums'] */
<<set $result to $fruits1.concat('Pears')>>
/* Returns ['Apples', 'Oranges', 'Pears'] */
<<set $result to $fruits1.concat('Pears', 'Pears')>>
/* Returns ['Apples', 'Oranges', 'Pears', 'Pears'] */
<<set $result to $fruits1.concat($fruits2, 'Pears')>>
/* Returns ['Apples', 'Oranges', 'Pears', 'Plums', 'Pears'] */
// Given the following:
let fruits1 = ['Apples', 'Oranges'];
let fruits2 = ['Pears', 'Plums'];
let result = fruits1.concat(fruits2);
// Returns ['Apples', 'Oranges', 'Pears', 'Plums']
let result = fruits1.concat(fruits2, fruits2);
// Returns ['Apples', 'Oranges', 'Pears', 'Plums', 'Pears', 'Plums']
let result = fruits1.concat('Pears');
// Returns ['Apples', 'Oranges', 'Pears']
let result = fruits1.concat('Pears', 'Pears');
// Returns ['Apples', 'Oranges', 'Pears', 'Pears']
let result = fruits1.concat(fruits2, 'Pears');
// Returns ['Apples', 'Oranges', 'Pears', 'Plums', 'Pears']
<Array>.concatUnique(members…)
→ Array<any>
Concatenates one or more unique members to the end of the base array and returns the result as a new array. Does not modify the original.
v2.21.0
: Introduced.members
: (any
…) The members to concatenate. Members that are arrays will be merged—i.e., their members will be concatenated, rather than the array itself.A new Array
formed from concatenating all unique array members in order.
/* Given the following: */
<<set $fruits1 to ['Apples', 'Oranges']>>
<<set $fruits2 to ['Pears', 'Plums']>>
<<set $result to $fruits1.concatUnique($fruits2)>>
/* Returns ['Apples', 'Oranges', 'Pears', 'Plums'] */
<<set $result to $fruits1.concatUnique($fruits2, $fruits2)>>
/* Returns ['Apples', 'Oranges', 'Pears', 'Plums'] */
<<set $result to $fruits1.concatUnique('Pears')>>
/* Returns ['Apples', 'Oranges', 'Pears'] */
<<set $result to $fruits1.concatUnique('Pears', 'Pears')>>
/* Returns ['Apples', 'Oranges', 'Pears'] */
<<set $result to $fruits1.concatUnique($fruits2, 'Pears')>>
/* Returns ['Apples', 'Oranges', 'Pears', 'Plums'] */
// Given the following:
let fruits1 = ['Apples', 'Oranges'];
let fruits2 = ['Pears', 'Plums'];
let result = fruits1.concatUnique(fruits2);
// Returns ['Apples', 'Oranges', 'Pears', 'Plums']
let result = fruits1.concatUnique(fruits2, fruits2);
// Returns ['Apples', 'Oranges', 'Pears', 'Plums']
let result = fruits1.concatUnique('Pears');
// Returns ['Apples', 'Oranges', 'Pears']
let result = fruits1.concatUnique('Pears', 'Pears');
// Returns ['Apples', 'Oranges', 'Pears']
let result = fruits1.concatUnique(fruits2, 'Pears');
// Returns ['Apples', 'Oranges', 'Pears', 'Plums']
<Array>.count(needle [, position])
→ integer number
Returns the number of times that the given member was found within the array, starting the search at position
.
v2.0.0
: Introduced.needle
: (any
) The member to count.position
: (optional, integer number
) The zero-based index at which to begin searching for needle
. If omitted, will default to 0
.An integer number
whose value is the number of times the given member was found within the array.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Plums', 'Oranges']>>
<<set $result to $fruits.count('Oranges')>>
/* Returns 2 */
<<set $result to $fruits.count('Oranges', 2)>>
/* Returns 1 */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Plums', 'Oranges'];
let result = fruits.count('Oranges');
// Returns 2
let result = fruits.count('Oranges', 2);
// Returns 1
<Array>.countWith(predicate [, thisArg])
→ integer number
Returns the number of times that members within the array pass the test implemented by the given predicate function.
v2.36.0
: Introduced.predicate
: (Function
) The function used to test each member. It is called with three arguments:
value
: (any
) The member being processed.index
: (optional, integer number
) The index of member being processed.array
: (optional, array
) The array being processed.thisArg
: (optional, any
) The value to use as this
when executing predicate
.An integer number
whose value is the number of times members passed the test.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Plums', 'Oranges']>>
<<set $result to $fruits.countWith((fruit) => fruit === 'Oranges')>>
/* Returns 2 */
/* Given the following: */
<<set $numbers to [1, 2.3, 4, 76, 3.1]>>
<<set $result to $numbers.countWith(Number.isInteger)>>
/* Returns 3 */
/* Given the following: */
<<set $items to [
{ name : 'Arming sword', kind : 'weapon' },
{ name : 'Crested helm', kind : 'armor' },
{ name : 'Dead rat', kind : 'junk' },
{ name : 'Healing potion', kind : 'potion' }
]>>
<<set $result to $items.countWith((item) => item.kind === 'junk')>>
/* Returns 1 */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Plums', 'Oranges'];
let result = fruits.countWith((fruit) => fruit === 'Oranges');
// Returns 2
// Given the following:
let numbers = [1, 2.3, 4, 76, 3.1];
let result = numbers.countWith(Number.isInteger);
// Returns 3
// Given the following:
let items = [
{ name : 'Arming sword', kind : 'weapon' },
{ name : 'Crested helm', kind : 'armor' },
{ name : 'Dead rat', kind : 'junk' },
{ name : 'Healing potion', kind : 'potion' }
];
let result = items.countWith((item) => item.kind === 'junk');
// Returns 1
<Array>.deleteAll(needles…)
→ Array<any>
Removes all instances of the given members from the array and returns a new array containing the removed members.
v2.37.0
: Introduced.needles
: (any
… | Array<any>
) The members to remove. May be a list of members or an array.A new Array
containing the removed members.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Plums', 'Oranges']>>
<<set $result to $fruits.deleteAll('Oranges')>>
/* Returns ['Oranges', 'Oranges'] */
/* $fruits ['Apples', 'Plums'] */
<<set $result to $fruits.deleteAll('Apples', 'Plums')>>
/* Returns ['Apples', 'Plums'] */
/* $fruits ['Oranges', 'Oranges'] */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Plums', 'Oranges'];
let result = fruits.deleteAll('Oranges');
// Returns ['Oranges', 'Oranges']
// $fruits ['Apples', 'Plums']
let result = fruits.deleteAll('Apples', 'Plums');
// Returns ['Apples', 'Plums']
// $fruits ['Oranges', 'Oranges']
<Array>.deleteAt(indices…)
→ Array<any>
Removes all of the members at the given indices from the array and returns a new array containing the removed members.
v2.5.0
: Introduced.indices
: (integer number
… | integers Array<number>
) The indices of the members to remove. May be a list or array of indices.A new Array
containing the removed members.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Plums', 'Oranges']>>
<<set $result to $fruits.deleteAt(2)>>
/* Returns ['Plums'] */
/* $fruits ['Apples', 'Oranges', 'Oranges'] */
<<set $result to $fruits.deleteAt(1, 3)>>
/* Returns ['Oranges', 'Oranges'] */
/* $fruits ['Apples', 'Plums'] */
<<set $result to $fruits.deleteAt(0, 2)>>
/* Returns ['Apples', 'Plums'] */
/* $fruits ['Oranges', 'Oranges'] */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Plums', 'Oranges'];
let result = fruits.deleteAt(2);
// Returns ['Plums']
// fruits ['Apples', 'Oranges', 'Oranges']
let result = fruits.deleteAt(1, 3);
// Returns ['Oranges', 'Oranges']
// fruits ['Apples', 'Plums']
let result = fruits.deleteAt(0, 2);
// Returns ['Apples', 'Plums']
// fruits ['Oranges', 'Oranges']
<Array>.deleteFirst(needles…)
→ Array<any>
Removes the first instance of the given members from the array and returns a new array containing the removed members.
v2.37.0
: Introduced.needles
: (any
… | Array<any>
) The members to remove. May be a list of members or an array.A new Array
containing the removed members.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Plums', 'Oranges']>>
<<set $result to $fruits.deleteFirst('Oranges')>>
/* Returns ['Oranges'] */
/* $fruits ['Apples', 'Plums', 'Oranges'] */
<<set $result to $fruits.deleteFirst('Apples', 'Plums')>>
/* Returns ['Apples', 'Plums'] */
/* $fruits ['Oranges', 'Oranges'] */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Plums', 'Oranges'];
let result = fruits.deleteFirst('Oranges');
// Returns ['Oranges']
// fruits ['Apples', 'Plums', 'Oranges']
let result = fruits.deleteFirst('Apples', 'Plums');
// Returns ['Apples', 'Plums']
// fruits ['Oranges', 'Oranges']
<Array>.deleteLast(needles…)
→ Array<any>
Removes the last instance of the given members from the array and returns a new array containing the removed members.
v2.37.0
: Introduced.needles
: (any
… | Array<any>
) The members to remove. May be a list of members or an array.A new Array
containing the removed members.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Plums', 'Oranges']>>
<<set $result to $fruits.deleteLast('Oranges')>>
/* Returns ['Oranges'] */
/* $fruits ['Apples', 'Oranges', 'Plums'] */
<<set $result to $fruits.deleteLast('Apples', 'Plums')>>
/* Returns ['Apples', 'Plums'] */
/* $fruits ['Oranges', 'Oranges'] */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Plums', 'Oranges'];
let result = fruits.deleteLast('Oranges');
// Returns ['Oranges']
// fruits ['Apples', 'Oranges', 'Plums']
let result = fruits.deleteLast('Apples', 'Plums');
// Returns ['Apples', 'Plums']
// fruits ['Oranges', 'Oranges']
<Array>.deleteWith(predicate [, thisArg])
→ Array<any>
Removes all of the members from the array that pass the test implemented by the given predicate function and returns a new array containing the removed members.
v2.25.0
: Introduced.predicate
: (Function
) The function used to test each member. It is called with three arguments:
value
: (any
) The member being processed.index
: (optional, integer number
) The index of member being processed.array
: (optional, array
) The array being processed.thisArg
: (optional, any
) The value to use as this
when executing predicate
.A new Array
containing the removed members.
Usage with primitive values.
/* Given the following: */
<<set $fruits to ['Apples', 'Apricots', 'Oranges']>>
$fruits.deleteWith((val) => val === 'Apricots')
/* Returns ['Apricots'] */
/* $fruits ['Apples', 'Oranges'] */
$fruits.deleteWith((val) => val.startsWith('Ap'))
/* Returns ['Apples', 'Apricots'] */
/* $fruits ['Oranges'] */
Usage with object values.
/* Given the following: */
<<set $fruits to [{ name : 'Apples' }, { name : 'Apricots' }, { name : 'Oranges' }]>>
$fruits.deleteWith((val) => val.name === 'Apricots')
/* Returns [{ name : 'Apricots' }] */
/* $fruits [{ name : 'Apples' }, { name : 'Oranges' }] */
$fruits.deleteWith((val) => val.name.startsWith('Ap'))
/* Returns [{ name : 'Apples' }, { name : 'Apricots' }] */
/* $fruits [{ name : 'Oranges' }] */
Usage with primitive values.
// Given the following:
let fruits = ['Apples', 'Apricots', 'Oranges'];
let result = fruits.deleteWith((val) => val === 'Apricots');
// Returns ['Apricots']
// fruits ['Apples', 'Oranges']
let result = fruits.deleteWith((val) => val.startsWith('Ap'));
// Returns ['Apples', 'Apricots']
// fruits ['Oranges']
Usage with object values.
// Given the following:
let fruits = [{ name : 'Apples' }, { name : 'Apricots' }, { name : 'Oranges' }];
let result = fruits.deleteWith((val) => val.name === 'Apricots');
// Returns [{ name : 'Apricots' }]
// fruits [{ name : 'Apples' }, { name : 'Oranges' }]
let result = fruits.deleteWith((val) => val.name.startsWith('Ap'));
// Returns [{ name : 'Apples' }, { name : 'Apricots' }]
// fruits [{ name : 'Oranges' }]
<Array>.first()
→ any
Returns the first member from the array. Does not modify the original.
v2.27.0
: Introduced.The first member's value (any
).
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
<<set $result to $pies.first()>>
/* Returns 'Blueberry' */
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
let result = pies.first();
// Returns 'Blueberry'
<Array>.flat(depth)
→ Array<any>
Returns a new array consisting of the source array with all sub-array elements concatenated into it recursively up to the given depth. Does not modify the original.
depth
: (optional, integer number
) The number of nested array levels should be flattened. If omitted, will default to 1
.A new Array
consisting of all members flattened up to the given depth.
/* Given the following: */
<<set $npa to [['Alfa', 'Bravo'], [[['Charlie'], 'Delta'], ['Echo']], 'Foxtrot']>>
<<set $result to $npa.flat()>>
/* Returns ['Alfa', 'Bravo', [['Charlie'], 'Delta'], ['Echo'], 'Foxtrot'] */
<<set $result to $npa.flat(1)>>
/* Returns ['Alfa', 'Bravo', [['Charlie'], 'Delta'], ['Echo'], 'Foxtrot'] */
<<set $result to $npa.flat(2)>>
/* Returns ['Alfa', 'Bravo', ['Charlie'], 'Delta', 'Echo', 'Foxtrot'] */
<<set $result to $npa.flat(Infinity)>>
/* Returns ['Alfa', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot'] */
// Given the following:
let npa = [['Alfa', 'Bravo'], [[['Charlie'], 'Delta'], ['Echo']], 'Foxtrot'];
let result = npa.flat();
// Returns ['Alfa', 'Bravo', [['Charlie'], 'Delta'], ['Echo'], 'Foxtrot']
let result = npa.flat(1);
// Returns ['Alfa', 'Bravo', [['Charlie'], 'Delta'], ['Echo'], 'Foxtrot']
let result = npa.flat(2);
// Returns ['Alfa', 'Bravo', ['Charlie'], 'Delta', 'Echo', 'Foxtrot']
let result = npa.flat(Infinity);
// Returns ['Alfa', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot']
<Array>.flatMap(callback [, thisArg])
→ Array<any>
Returns a new array consisting of the result of calling the given mapping function on every element in the source array and then concatenating all sub-array elements into it recursively up to a depth of 1
. Does not modify the original.
Note:
Identical to calling <Array>.map(…).flat()
.
callback
: (Function
) The function used to produce members of the new array. It is called with three arguments:
value
: (any
) The member being processed.index
: (optional, integer number
) The index of member being processed.array
: (optional, array
) The array being processed.thisArg
: (optional, any
) The value to use as this
when executing callback
.A new Array
consisting of all members flattened up to the given depth.
/* Given the following: */
<<set $npa to ['Alfa', 'Bravo Charlie', 'Delta Echo Foxtrot']>>
<<set $result to $npa.flatMap((val) => val.split(' '))>>
/* Returns ['Alfa', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot'] */
// Given the following:
let npa = ['Alfa', 'Bravo Charlie', 'Delta Echo Foxtrot'];
let result = npa.flatMap((val) => val.split(' '));
// Returns ['Alfa', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot']
<Array>.includes(needle [, position])
→ boolean
Returns whether the given member was found within the array, starting the search at position
.
needle
: (any
) The member to find.position
: (optional, integer number
) The zero-based index at which to begin searching for needle
. If omitted, will default to 0
.A boolean
denoting whether the given member was found within the array.
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
<<set $result to $pies.includes('Cherry')>>
/* Returns true */
<<set $result to $pies.includes('Pecan', 3)>>
/* Returns true */
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
let result = pies.includes('Cherry');
// Returns true
let result = pies.includes('Pecan', 3);
// Returns true
<Array>.includesAll(needles…)
→ boolean
Returns whether all of the given members were found within the array.
v2.10.0
: Introduced.needles
: (any
… | Array<any>
) The members to find. May be a list of members or an array.A boolean
denoting whether all of the given members were found within the array.
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
<<set $result to $pies.includesAll('Cherry', 'Raspberry')>>
/* Returns false */
<<set $result to $pies.includesAll('Blueberry', 'Cream')>>
/* Returns true */
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
let result = pies.includesAll('Cherry', 'Raspberry');
// Returns false
let result = pies.includesAll('Blueberry', 'Cream');
// Returns true
<Array>.includesAny(needles…)
→ boolean
Returns whether any of the given members were found within the array.
v2.10.0
: Introduced.needles
: (any
… | Array<any>
) The members to find. May be a list of members or an array.A boolean
denoting whether any of the given members were found within the array.
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
<<set $result to $pies.includesAny('Cherry', 'Coconut')>>
/* Returns true */
<<set $result to $pies.includesAny('Coconut')>>
/* Returns false */
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
let result = pies.includesAny('Cherry', 'Coconut');
// Returns true
let result = pies.includesAny('Coconut');
// Returns false
<Array>.last()
→ any
Returns the last member from the array. Does not modify the original.
v2.27.0
: Introduced.The last member's value (any
).
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
<<set $result to $pies.last()>>
/* Returns 'Pumpkin' */
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
let result = $pies.last();
// Returns 'Pumpkin'
<Array>.pluck()
→ any
Removes and returns a random member from the base array.
v2.0.0
: Introduced.The removed member's value (any
).
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
<<set $result to $pies.pluck()>>
/* Removes and returns a random pie from the array */
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
// Removes and returns a random pie from the array
let result = $pies.pluck();
<Array>.pluckMany(want)
→ Array<any>
Randomly removes the given number of members from the base array and returns the removed members as a new array.
v2.20.0
: Introduced.want
: (integer number
) The number of members to pluck. Cannot pluck more members than the base array contains.A new Array
containing the randomly removed members.
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']
/* Removes three random pies from the array and returns them as a new array */
<<set $result to $pies.pluckMany(3)>>
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
// Removes three random pies from the array and returns them as a new array
let result = pies.pluckMany(3);
<Array>.pop()
→ any
Removes and returns the last member from the array, or undefined
if the array is empty.
The last member's value (any
).
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Pears']>>
<<set $result to $fruits.pop()>>
/* Returns 'Pears' */
/* $fruits ['Apples', 'Oranges'] */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Pears'];
let result = fruits.pop();
// Returns 'Pears'
// fruits ['Apples', 'Oranges']
<Array>.push(members…)
→ integer number
Appends one or more members to the end of the base array and returns its new length.
members
: (any
…) The members to append.An integer number
whose value is the new length of the array.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges']>>
<<set $result to $fruits.push('Apples')>>
/* Returns 3 */
/* $fruits ['Apples', 'Oranges', 'Apples'] */
<<set $result to $fruits.push('Plums', 'Plums')>>
/* Returns 4 */
/* $fruits ['Apples', 'Oranges', 'Plums', 'Plums'] */
// Given the following:
let fruits = ['Apples', 'Oranges'];
let result = fruits.push('Apples');
// Returns 3
// fruits ['Apples', 'Oranges', 'Apples']
let result = fruits.push('Plums', 'Plums');
// Returns 4
// fruits ['Apples', 'Oranges', 'Plums', 'Plums']
<Array>.pushUnique(members…)
→ integer number
Appends one or more unique members to the end of the base array and returns its new length.
v2.21.0
: Introduced.members
: (any
…) The members to append.An integer number
whose value is the new length of the array.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges']>>
<<set $result to $fruits.pushUnique('Apples')>>
/* Returns 2 */
/* $fruits ['Apples', 'Oranges'] */
<<set $result to $fruits.pushUnique('Plums', 'Plums')>>
/* Returns 3 */
/* $fruits ['Apples', 'Oranges', 'Plums'] */
// Given the following:
let fruits = ['Apples', 'Oranges'];
let result = fruits.pushUnique('Apples');
// Returns 2
// fruits ['Apples', 'Oranges']
let result = fruits.pushUnique('Plums', 'Plums');
// Returns 3
// fruits ['Apples', 'Oranges', 'Plums']
<Array>.random()
→ any
Returns a random member from the base array. Does not modify the original.
v2.0.0
: Introduced.The selected member's value (any
).
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
/* Returns a random pie from the array */
<<set $result to $pies.random()>>
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
// Returns a random pie from the array
let result = pies.random();
<Array>.randomMany(want)
→ Array<any>
Randomly selects the given number of unique members from the base array and returns the selected members as a new array. Does not modify the original.
v2.20.0
: Introduced.want
: (integer number
) The number of members to select. Cannot select more members than the base array contains.A new Array
containing the randomly selected members.
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
/* Returns a new array containing three unique random pies from the array */
<<set $result to $pies.randomMany(3)>>
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
// Returns a new array containing three unique random pies from the array
let result = pies.randomMany(3);
<Array>.shift()
→ any
Removes and returns the first member from the array, or undefined
if the array is empty.
The first member's value (any
) or undefined
, if the array is empty.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Pears']>>
<<set $result to $fruits.shift()>>
/* Returns 'Apples' */
/* $fruits ['Oranges', 'Pears'] */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Pears'];
let result = fruits.shift();
// Returns 'Apples'
// fruits ['Oranges', 'Pears']
<Array>.shuffle()
→ Array<any>
Randomly shuffles the array.
v2.0.0
: Introduced.The original Array
randomly shuffled.
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
/* Randomizes the order of the array */
<<run $pies.shuffle()>>
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
// Randomizes the order of the array
pies.shuffle();
<Array>.toShuffled()
→ Array<any>
Returns a new copy of the base array created by shuffling the array. Does not modify the original.
v2.37.0
: Introduced.A new Array
consisting of the original array randomly shuffled.
/* Given the following: */
<<set $pies to ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin']>>
/* Randomizes the order of the array without modifying the original */
<<set $result to $pies.toShuffled()>>
// Given the following:
let pies = ['Blueberry', 'Cherry', 'Cream', 'Pecan', 'Pumpkin'];
// Randomizes the order of the array without modifying the original
let result = pies.toShuffled();
<Array>.toUnique()
→ Array<any>
Returns a new copy of the base array created by removing all duplicate members. Does not modify the original.
v2.37.0
: Introduced.A new Array
consisting of the original array with all duplicates removed.
/* Given the following: */
<<set $fruits to ['Apples', 'Oranges', 'Plums', 'Plums', 'Apples']>>
<<set $result to $fruits.toUnique()>>
/* Returns ['Apples', 'Oranges', 'Plums'] */
// Given the following:
let fruits = ['Apples', 'Oranges', 'Plums', 'Plums', 'Apples'];
let result = fruits.toUnique();
// Returns ['Apples', 'Oranges', 'Plums']
<Array>.unshift(members…)
→ integer number
Prepends one or more members to the beginning of the base array and returns its new length.
members
: (any
…) The members to append.An integer number
whose value is the new length of the array.
/* Given the following: */
<<set $fruits to ['Oranges', 'Plums']>>
<<set $result to $fruits.unshift('Oranges')>>
/* Returns 3 */
/* $fruits ['Oranges', 'Oranges', 'Plums'] */
<<set $result to $fruits.unshift('Apples', 'Apples')>>
/* Returns 4 */
/* $fruits ['Apples', 'Apples', 'Oranges', 'Plums'] */
// Given the following:
let fruits = ['Oranges', 'Plums'];
let result = fruits.unshift('Oranges');
// Returns 3
// fruits ['Oranges', 'Oranges', 'Plums']
let result = fruits.unshift('Apples', 'Apples');
// Returns 4
// fruits ['Apples', 'Apples', 'Oranges', 'Plums']
<Array>.unshiftUnique(members…)
→ integer number
Prepends one or more unique members to the beginning of the base array and returns its new length.
v2.21.0
: Introduced.members
: (any
…) The members to append.An integer number
whose value is the new length of the array.
/* Given the following: */
<<set $fruits to ['Oranges', 'Plums']>>
<<set $result to $fruits.unshiftUnique('Oranges')>>
/* Returns 2 */
/* $fruits ['Oranges', 'Plums'] */
<<set $result to $fruits.unshiftUnique('Apples', 'Apples')>>
/* Returns 3 */
/* $fruits ['Apples', 'Oranges', 'Plums'] */
// Given the following:
let fruits = ['Oranges', 'Plums'];
let result = fruits.unshiftUnique('Oranges');
// Returns 2
// fruits ['Oranges', 'Plums']
let result = fruits.unshiftUnique('Apples', 'Apples');
// Returns 3
// fruits ['Apples', 'Oranges', 'Plums']
<Array>.delete(needles…)
→ Array<any>
Deprecated:
This instance method has been deprecated and should no longer be used. See the <Array>.deleteAll()
instance method.
v2.5.0
: Introduced.v2.37.0
: Deprecated in favor of <Array>.deleteAll()
.<jQuery>.ariaClick([options ,] handler)
→ jQuery
Makes the target element(s) WAI-ARIA-compatible clickables—meaning that various accessibility attributes are set and, in addition to mouse clicks, enter/return and spacebar key presses also activate them. Returns a reference to the current jQuery
instance for chaining.
v2.0.0
: Introduced.v2.37.0
: Add tabindex
option.options
: (optional, Object
) The options to be used when creating the clickables. See below for details.handler
: (Function
) The callback to invoke when the target element(s) are activated.An options object should have some of the following properties:
namespace
: (string
) A period-separated list of event namespaces.one
: (boolean
) Whether the clickables are single-use—i.e., the handler callback runs only once and then removes itself. If omitted, defaults to false
.selector
: (string
) A selector applied to the target element(s) to filter the descendants that triggered the event. If omitted or null
, the event is always handled when it reaches the target element(s).data
: (any
) Data to be passed to the handler in event.data
when an event is triggered.tabindex
: (integer number
) Value for the tabindex
attribute. If omitted, defaults to 0
.controls
: (string
) Value for the aria-controls
attribute.pressed
: (string
) Value for the aria-pressed
attribute (valid values: "true"
, "false"
).label
: (string
) Value for the aria-label
and title
attributes.The current jQuery
instance.
Note:
The macro examples would be exactly the same as the JavaScript examples, just wrapped in a <<script>>
macro.
// Given an existing element: <a id="so-clicky">Click me</a>
$('#so-clicky').ariaClick((event) => {
/* do stuff */
});
// Creates a basic link and appends it to the `output` element
$('<a>Click me</a>')
.ariaClick((event) => {
/* do stuff */
})
.appendTo(output);
// Creates a basic button and appends it to the `output` element
$('<button>Click me</button>')
.ariaClick((event) => {
/* do stuff */
})
.appendTo(output);
// Creates a link with options and appends it to the `output` element
$('<a>Click me</a>')
.ariaClick({
one : true,
label : 'This single-use link does stuff.'
}, (event) => {
/* do stuff */
})
.appendTo(output);
<jQuery>.ariaDisabled(state)
→ jQuery
Changes the disabled state of the target WAI-ARIA-compatible clickable element(s). Returns a reference to the current jQuery
instance for chaining.
Note:
This method is meant to work with clickables created via <jQuery>.ariaClick()
and may not work with clickables from other sources. SugarCube uses <jQuery>.ariaClick()
internally to handle all of its various link markup and macros.
v2.26.0
: Introduced.state
: (boolean
) The disabled state to apply. Truthy to disable the element(s), falsy to enable them.The current jQuery
instance.
/* Given an existing WAI-ARIA-compatible clickable element with the ID "so-clicky" */
/* Disables the target element */
<<run $('#so-clicky').ariaDisabled(true)>>
/* Enables the target element */
<<run $('#so-clicky').ariaDisabled(false)>>
/* Given an existing WAI-ARIA-compatible clickable element with the ID "so-clicky" */
// Disables the target element
$('#so-clicky').ariaDisabled(true);
// Enables the target element
$('#so-clicky').ariaDisabled(false);
<jQuery>.ariaIsDisabled()
→ boolean
Returns whether any of the target WAI-ARIA-compatible clickable element(s) are disabled.
Note:
This method is meant to work with clickables created via <jQuery>.ariaClick()
and may not work with clickables from other sources. SugarCube uses <jQuery>.ariaClick()
internally to handle all of its various link markup and macros.
v2.26.0
: Introduced.A boolean
denoting whether any of the elements are disabled.
/* Given an existing WAI-ARIA-compatible clickable element with the ID "so-clicky" */
<<set $result to $('#so-clicky').ariaIsDisabled()>>
/* Returns true, if "#so-clicky" is disabled */
<<set $result to $('#so-clicky').ariaIsDisabled()>>
/* Returns false, if "#so-clicky" is enabled */
/* Given an existing WAI-ARIA-compatible clickable element with the ID "so-clicky" */
let result = $('#so-clicky').ariaIsDisabled();
// Returns true, if "#so-clicky" is disabled
let result = $('#so-clicky').ariaIsDisabled();
// Returns false, if "#so-clicky" is enabled
jQuery.wiki(sources…)
Wikifies the given content source(s) and discards the result. If there were errors, an exception is thrown. This is only really useful when you want to invoke a macro for its side-effects and aren't interested in its output.
v2.17.0
: Introduced.sources
: (string
…) The list of content sources./* Invokes the <<somemacro>> macro, discarding any output */
<<run $.wiki('<<somemacro>>')>>
// Invokes the <<somemacro>> macro, discarding any output
$.wiki('<<somemacro>>');
jQuery.wikiPassage(passageName)
Wikifies the passage by the given name and discards the result. If there were errors, an exception is thrown. This is only really useful when you want to invoke a macro for its side-effects and aren't interested in its output.
v2.37.0
: Introduced.passageName
: (string
) The name of the passage./* Renders the passage, discarding any output */
<<run $.wikiPassage('Fight Init')>>
// Renders the passage, discarding any output
$.wikiPassage('Fight Init');
<jQuery>.wiki(sources…)
→ jQuery
Wikifies the given content source(s) and appends the result to the target element(s). Returns a reference to the current jQuery
instance for chaining.
v2.0.0
: Introduced.sources
: (string
…) The list of content sources.The current jQuery
instance.
/* Given an element: <div id="the-box"></div> */
/* Appends "Who <em>are</em> you?" to the target element */
<<run $('#the-box').wiki('Who //are// you?')>>
/* Given an element: <div id="the-box"></div> */
// Appends "Who <em>are</em> you?" to the target element
$('#the-box').wiki('Who //are// you?');
<jQuery>.wikiPassage(passageName)
→ jQuery
Wikifies the passage by the given name and appends the result to the target element(s). Returns a reference to the current jQuery
instance for chaining.
v2.37.0
: Introduced.passageName
: (string
) The name of the passage.The current jQuery
instance.
/* Given an element: <div id="notebook"></div> */
/* Appends the rendered passage to the target element */
<<run $('#notebook').wikiPassage('Notes')>>
/* Given an element: <div id="notebook"></div> */
// Appends the rendered passage to the target element
$('#notebook').wikiPassage('Notes');
JSON.reviveWrapper(code [, data])
→ Array
Deprecated:
This static method has been deprecated and should no longer be used. See the Serial.createReviver()
static method.
v2.0.0
: Introduced.v2.9.0
: Added data
parameter.v2.37.0
: Deprecated in favor of Serial.createReviver()
.Math.clamp(num , min , max)
→ number
Returns the given number clamped to the specified bounds. Does not modify the original.
v2.0.0
: Introduced.num
: (number
) The number to clamp. May be an actual number or a numerical string.min
: (number
) The lower bound of the number.max
: (number
) The upper bound of the number.A new number
.
/* Returns a copy of the original clamped to the bounds 0–200 */
<<set $result to Math.clamp($stat, 0, 200)>>
/* Returns a copy of the original clamped to the bounds 1–6.6 */
<<set $result to Math.clamp($stat, 1, 6.6)>>
// Returns a copy of the original clamped to the bounds 0–200
let result = Math.clamp(stat, 0, 200);
// Returns a copy of the original clamped to the bounds 1–6.6
let result = Math.clamp(stat, 1, 6.6);
Math.trunc(num)
→ integer number
Returns the whole (integer) part of the given number by removing its fractional part, if any. Does not modify the original.
num
: (number
) The number to truncate to an integer.A new integer number
.
<<set $result to Math.trunc(12.7)>>
/* Returns 12 */
<<set $result to Math.trunc(-12.7)>>
/* Returns -12 */
let result = Math.trunc(12.7);
// Returns 12
let result = Math.trunc(-12.7);
// Returns -12
<Number>.clamp(min , max)
→ number
Deprecated:
This static method has been deprecated and should no longer be used. See the Math.clamp()
static method.
v2.0.0
: Introduced.v2.37.0
: Deprecated.RegExp.escape(text)
→ string
Returns the given string with all regular expression metacharacters escaped. Does not modify the original.
text
: (string
) The string to escape.A new string
that can be safely used as a literal pattern.
<<set $result to RegExp.escape('That will be $15, cash only.')>>
/* Returns '\x54hat\x20will\x20be\x20\$15\x2c\x20cash\x20only\.' */
let result = RegExp.escape('That will be $15, cash only.');
// Returns '\x54hat\x20will\x20be\x20\$15\x2c\x20cash\x20only\.'
Serial
Methods Serial.createReviver(code [, data])
→ ArrayReturns the given code string, and optional data, wrapped within the deserialization reviver. Intended to allow authors to easily create the reviver required to revive their custom object types (classes). The reviver should be returned from an object instance's .toJSON()
method, so that the instance may be properly revived upon deserialization.
See: The Non-generic object types (classes) guide for more detailed information.
v2.37.0
: Introduced.code
: (string
) The revival code string.data
: (optional, any
) The data that should be made available to the evaluated revival code during deserialization via the special $ReviveData$
variable. WARNING: Attempting to pass the value of an object instance's this
directly as the reviveData
parameter will trigger out of control recursion in the serializer, so a clone of the instance's own data must be passed instead.A new string
containing the serialized code.
Note:
The macro examples would be exactly the same as the JavaScript examples, just wrapped in a <<script>>
macro.
Serial.createReviver(/* JavaScript code string */); // without data chunk
Serial.createReviver(/* JavaScript code string */, myOwnData); // with data chunk
// Assume that you're attempting to revive an instance of a custom class named
// `Character`, which is assigned to a story variable named `$pc`. The call
// to `Serial.createReviver()` might look something like the following.
const ownData = {};
Object.keys(this).forEach((pn) => ownData[pn] = clone(this[pn]));
return Serial.createReviver('new Character($ReviveData$)', ownData);
Note: Strings in TwineScript/JavaScript are Unicode, however, due to historic reasons they are comprised of, and indexed by, individual UTF-16 code units rather than code points. This means that some code points may span multiple code units—e.g., the emoji 💩 is one code point, but two code units.
<String>.count(needle [, position])
→ integer number
Returns the number of times that the given substring was found within the string, starting the search at position
.
See: String methods note.
v2.0.0
: Introduced.needle
: (any
) The substring to count.position
: (optional, integer number
) The zero-based index at which to begin searching for needle
. If omitted, will default to 0
.An integer number
denoting the number of times that the given substring was found within the string.
/* Given the following: */
<<set $text to 'How now, brown cow.'>>
<<set $result to $text.count('ow')>>
/* Returns 4 */
<<set $result to $text.count('ow', 8)>>
/* Returns 2 */
// Given the following:
let text = 'How now, brown cow.';
let result = text.count('ow');
// Returns 4
let result = text.count('ow', 8);
// Returns 2
<String>.first()
→ string
Returns the first Unicode code point within the string. Does not modify the original.
See: String methods note.
v2.27.0
: Introduced.A new string
containing the first Unicode code point.
/* Given the following: */
<<set $text to 'abc'>>
<<set $result to $text.first()>>
/* Returns 'a' */
/* Given the following: */
<<set $text to '🙈🙉🙊'>>
<<set $result to $text.first()>>
/* Returns '🙈' */
// Given the following:
let text = 'abc';
let result = text.first();
// Returns 'a'
// Given the following:
let text = '🙈🙉🙊';
let result = text.first();
// Returns '🙈'
String.format(format , arguments…)
→ string
Returns a formatted string, after replacing each format item in the given format string with the text equivalent of the corresponding argument's value.
v2.0.0
: Introduced.format
: (string
) The format string, which consists of normal text and format items.arguments
: (any
… | Array<any>
) Either a list of arguments, which correspond by-index to the format items within the format string, or an array, whose members correspond by-index.A format item has the syntax {index[,alignment]}
, square-brackets denoting optional elements.
index
: (integer number
) The (zero-based) index of the argument whose string representation will replace the format item.alignment
: (optional, integer number
) The total length of the field into which the argument is inserted, and whether it's right- or left-aligned (positive aligns right, negative aligns left).A new string
based on the format and arguments.
Using a list of arguments.
<<set $result to String.format('{0}, {1}!', 'Hello', 'World')>>
/* Returns 'Hello, World!' */
Using an array argument.
<<set $result to String.format('{0}, {1}!', ['Hello', 'World'])>>
/* Returns 'Hello, World!' */
Using alignments.
<<set $result to String.format('{0,6}', 'foo')>>
/* Returns ' foo' */
<<set $result to String.format('{0,-6}', 'foo')>>
/* Returns 'foo ' */
Using a list of arguments.
let result = String.format('{0}, {1}!', 'Hello', 'World');
// Returns 'Hello, World!'
Using an array argument.
let result = String.format('{0}, {1}!', ['Hello', 'World']);
// Returns 'Hello, World!'
Using alignments.
let result = String.format('{0,6}', 'foo');
// Returns ' foo'
let result = String.format('{0,-6}', 'foo');
// Returns 'foo '
<String>.includes(needle [, position])
→ boolean
Returns whether the given substring was found within the string, starting the search at position
.
See: String methods note.
needle
: (any
) The substring to find.position
: (optional, integer number
) The zero-based index at which to begin searching for needle
. If omitted, will default to 0
.A boolean
denoting whether the given substring was found within the string.
/* Given the following: */
<<set $text to 'How now, brown cow.'>>
<<set $result to $text.includes('row')>>
/* Returns true */
<<set $result to $text.includes('row', 14)>>
/* Returns false */
<<set $result to $text.includes('cow', 14)>>
/* Returns true */
<<set $result to $text.includes('pow')>>
/* Returns false */
// Given the following:
let text = 'How now, brown cow.';
let result = text.includes('row');
// Returns true
let result = text.includes('row', 14);
// Returns false
let result = text.includes('cow', 14);
// Returns true
let result = text.includes('pow');
// Returns false
<String>.last()
→ string
Returns the last Unicode code point within the string. Does not modify the original.
See: String methods note.
v2.27.0
: Introduced.A new string
containing the last Unicode code point.
/* Given the following: */
<<set $text to 'abc'>>
<<set $result to $text.last()>>
/* Returns 'c' */
/* Given the following: */
<<set $text to '🙈🙉🙊'>>
<<set $result to $text.last()>>
/* Returns '🙊' */
// Given the following:
let text = 'abc';
let result = text.last();
// Returns 'c'
// Given the following:
let text = '🙈🙉🙊';
let result = text.last();
// Returns '🙊'
<String>.toLocaleUpperFirst()
→ string
Returns the string with its first Unicode code point converted to upper case, according to any locale-specific rules. Does not modify the original.
See: String methods note.
v2.9.0
: Introduced.A new string
with its first Unicode code point uppercased according to locale-specific rules.
Using the Turkish (Türkçe) locale.
/* Given the following: */
<<set $text to 'ışık'>>
<<set $result to $text.toLocaleUpperFirst()>>
/* Returns 'Işık' */
/* Given the following: */
<<set $text to 'iki'>>
<<set $result to $text.toLocaleUpperFirst()>>
/* Returns 'İki' */
Using the Turkish (Türkçe) locale.
// Given the following:
let text = 'ışık';
let result = text.toLocaleUpperFirst();
// Returns 'Işık'
// Given the following:
let text = 'iki';
let result = text.toLocaleUpperFirst();
// Returns 'İki'
<String>.toUpperFirst()
→ string
Returns the string with its first Unicode code point converted to upper case. Does not modify the original.
See: String methods note.
v2.9.0
: Introduced.A new string
with its first Unicode code point uppercased.
/* Given the following: */
<<set $text to 'hello.'>>
<<set $result to $text.toUpperFirst()>>
/* Returns 'Hello.'*/
/* Given the following: */
<<set $text to 'χαίρετε.'>>
<<set $result to $text.toUpperFirst()>>
/* Returns 'Χαίρετε.'*/
// Given the following:
let text = 'hello.';
let result = text.toUpperFirst();
// Returns 'Hello.'
// Given the following:
let text = 'χαίρετε.';
let result = text.toUpperFirst();
// Returns 'Χαίρετε.'
Passage, tag, and variable names that have special meaning to SugarCube.
Passages that are used only as code and should not be navigated to. They exist simply to fill in parts of the UI—e.g., StoryCaption
—or execute code at specific times—e.g., PassageReady
—or both—e.g., PassageHeader
.
PassageDone
Used for post-passage-display tasks, like redoing dynamic changes (happens after the rendering and display of each passage). Generates no output.
Roughly equivalent to the :passagedisplay
event.
v2.0.0
: Introduced.PassageFooter
Appended to each rendered passage.
Roughly equivalent to the :passagerender
event.
v2.0.0
: Introduced.PassageHeader
Prepended to each rendered passage.
Roughly equivalent to the :passagestart
event.
v2.0.0
: Introduced.PassageReady
Used for pre-passage-display tasks, like redoing dynamic changes (happens before the rendering of each passage). Generates no output.
Roughly equivalent to the :passagestart
event.
v2.0.0
: Introduced.StoryAuthor
Used to populate the authorial byline area in the UI bar (element ID: story-author
).
v2.0.0
: Introduced.StoryBanner
Used to populate the story's banner area in the UI bar (element ID: story-banner
).
v2.0.0
: Introduced.StoryCaption
Used to populate the story's caption area in the UI bar (element ID: story-caption
). May also be, and often is, used to add additional story UI elements and content to the UI bar.
v2.0.0
: Introduced.StoryDisplayTitle
Sets the story's display title in the browser's titlebar and the UI bar (element ID: story-title
). If omitted, the story title will be used instead.
v2.31.0
: Introduced.StoryInit
Used for pre-story-start initialization tasks, like variable initialization (happens at the beginning of story initialization). Generates no output.
v2.0.0
: Introduced.StoryInterface
Used to replace SugarCube's default UI. Its contents are treated as raw HTML markup—i.e., none of SugarCube's special HTML processing is performed. The markup is contained within a <div id="story" role="main">
element and must itself contain, at least, an element with the ID passages
that will be the main passage display area. For example:
<div id="story" role="main">
<!-- StoryInterface elements added here -->
</div>
Additional elements, aside from the #passages
element, may include either the data-init-passage
or data-passage
content attribute, whose value is the name of the passage used to populate the element—the passage will be processed as normal, meaning that markup and macros will work as expected. The data-init-passage
attribute causes the element to be updated once at initialization, while the data-passage
attribute causes the element to be updated upon each passage navigation.
Warning:
Elements that include either a data-init-passage
or data-passage
content attribute should not themselves contain additional elements. This is because such elements' contents are replaced via their associated passage, so any child elements will be lost.
v2.18.0
: Introduced.v2.28.0
: Added processing of the data-passage
content attribute.v2.36.0
: Added processing of the data-init-passage
content attribute.v2.37.0
: Fixed processing of the data-init-passage
content attribute. Added the <div#story>
container element.<div id="passages"></div>
Combined with the built-in wrapper:
<div id="story" role="main">
<div id="passages"></div>
</div>
data-init-passage
and data-passage
content attributes<div id="menu" data-init-passage="Menu"></div>
<div id="notifications" data-passage="Notifications"></div>
<div id="passages"></div>
Combined with the built-in wrapper:
<div id="story" role="main">
<div id="menu" data-init-passage="Menu"></div>
<div id="notifications" data-passage="Notifications"></div>
<div id="passages"></div>
</div>
StoryMenu
Used to populate the story's menu items in the UI bar (element ID: menu-story
).
Note:
The story menu only displays links—specifically, anything that creates an anchor element (<a>
). While it renders content just as any other passage does, instead of displaying the rendered output as-is, it sifts through the output and builds its menu from the generated links contained therein.
v2.0.0
: Introduced.[[Inventory]]
<<if not $inventory.isEmpty()>>
[[Inventory]]
<</if>>
<<link "Schedule">> … <</link>>
<<if $scheduleEnabled>>
<<link "Schedule">> … <</link>>
<</if>>
<a data-passage="…">Equipment<a>
<<if $equipped.length > 0>>
<a data-passage="…">Equipment<a>
<</if>>
StorySettings
Warning:
Twine 1.4 code passage unused by SugarCube. The Config
API serves the same basic purpose.
StorySubtitle
Sets the story's subtitle in the UI bar (element ID: story-subtitle
).
v2.0.0
: Introduced.StoryTitle
Warning: The story title is used to create the storage ID that is used to store all player data, both temporary and persistent. It should be plain text, containing no code, markup, or macros of any kind.
Tip:
If you want to set a title for display that contains code, markup, or macros, see the StoryDisplayTitle
code passage.
Twine 2: Unused, not a code passage. The story's title is part of the story project.
Twine 1/Twee: Required. Sets the story's title.
v2.0.0
: Introduced.StoryShare
Deprecated: This special passage has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.Passages that receive some kind of special treatment from the engine.
Note: Some special passages are conditional and may not always be special passages. The conditions will be noted within each such passsge's entry.
Start
Twine 2: Not a special passage. Any passage may be chosen as the starting passage by selecting it via the Start Story Here passage context-menu item—n.b., older versions of Twine 2 used a icon for the same purpose.
Twine 1/Twee: Required. The starting passage, the first passage displayed. Configurable, see Config.passages.start
for more information.
v2.0.0
: Introduced.Passages tagged with code tags are used only as code or data and cannot be navigated to.
Note: Some code tags are conditional and may not always act as code tags. The conditions will be noted within each such tag's entry.
init
Registers the passage as an initialization passage. Used for pre-story-start initialization tasks, like variable initialization (happens at the beginning of story initialization). Generates no output.
Note:
This is chiefly intended for use by add-ons/libraries. For normal projects, authors are strongly encouraged to continue to use the StoryInit
special named passage.
v2.36.0
: Introduced.script
Twine 2: Unused, not a code tag. Use the Edit Story JavaScript story editor menu item for scripts.
Twine 1/Twee: Registers the passage as JavaScript code, which is executed during startup.
v2.0.0
: Introduced.stylesheet
Twine 2: Unused, not a code tag. Use the Edit Story Stylesheet story editor menu item for styles.
Twine 1/Twee: Registers the passage as a CSS stylesheet, which is loaded during startup. It is strongly recommended that you use only one stylesheet passage. Additionally, see the tagged stylesheet warning.
v2.0.0
: Introduced.Twine.audio
Registers the passage as an audio passage. See Guide: Media Passages for more information.
v2.24.0
: Introduced.Twine.image
Registers the passage as an image passage. See Guide: Media Passages for more information.
v2.0.0
: Introduced.Twine.video
Registers the passage as a video passage. See Guide: Media Passages for more information.
v2.24.0
: Introduced.Twine.vtt
Registers the passage as a VTT passage. See Guide: Media Passages for more information.
v2.24.0
: Introduced.widget
Registers the passage as <<widget>>
macro definitions, which are loaded during startup.
v2.0.0
: Introduced.nobr
Causes leading/trailing newlines to be removed and all remaining sequences of newlines to be replaced with single spaces before the passage is rendered. Equivalent to wrapping the entire passage in a <<nobr>>
macro. See the Config.passages.nobr
setting for a way to apply the same processing to all passages at once.
Note:
Does not affect script
or stylesheet
tagged passages, for Twine 1/Twee.
v2.0.0
: Introduced.bookmark
Deprecated: This special tag has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.$
Alias for jQuery
, by default.
Note:
This should not be confused with story variables, which start with a $
—e.g., $foo
.
v2.0.0
: Introduced._args
Widget arguments array (only inside widgets). See <<widget>>
for more information.
v2.36.0
: Introduced._contents
Widget contents string (only inside block widgets). See <<widget>>
for more information.
v2.36.0
: Introduced.Config
Configuration API. See Config
API for more information.
v2.0.0
: Introduced.Dialog
Dialog API. See Dialog
API for more information.
v2.0.0
: Introduced.Engine
Engine API. See Engine
API for more information.
v2.0.0
: Introduced.Fullscreen
Fullscreen API. See Fullscreen
API for more information.
v2.31.0
: Introduced.jQuery
jQuery library function.
v2.0.0
: Introduced.l10nStrings
Strings localization object. See Localization for more information.
v2.10.0
: Introduced.LoadScreen
LoadScreen API. See LoadScreen
API for more information.
v2.15.0
: Introduced.Macro
Macro API. See Macro
API for more information.
v2.0.0
: Introduced.Passage
Passage API. See Passage
API for more information.
v2.0.0
: Introduced.Save
Save API. See Save
API for more information.
v2.0.0
: Introduced.Setting
Setting API. See Setting
API for more information.
v2.0.0
: Introduced.settings
Player settings object, set up by the author/developer. See Setting
API for more information.
v2.0.0
: Introduced.setup
Object that authors/developers may use to set up various bits of static data. Generally, you would use this for data that does not change and should not be stored within story variables, which would make it part of the history.
v2.0.0
: Introduced.SimpleAudio
SimpleAudio API. See SimpleAudio
API for more information.
v2.28.0
: Introduced.State
State API. See State
API for more information.
v2.0.0
: Introduced.Story
Story API. See Story
API for more information.
v2.0.0
: Introduced.Template
Template API. See Template
API for more information.
v2.29.0
: Introduced.UI
UI API. See UI
API for more information.
v2.0.0
: Introduced.UIBar
UIBar API. See UIBar
API for more information.
v2.17.0
: Introduced.$args
Deprecated:
The $args
special variable has been deprecated and should no longer be used. See the _args
special variable for its replacement.
v2.0.0
: Introduced.v2.36.0
: Deprecated.postdisplay
Deprecated:
postdisplay
tasks have been deprecated and should no longer be used. See the :passagedisplay
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.postrender
Deprecated:
postrender
tasks have been deprecated and should no longer be used. See the :passagerender
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.predisplay
Deprecated:
predisplay
tasks have been deprecated and should no longer be used. See the :passagestart
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.prehistory
Deprecated:
prehistory
tasks have been deprecated and should no longer be used. See the :passageinit
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.prerender
Deprecated:
prerender
tasks have been deprecated and should no longer be used. See the :passagestart
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.段落名称和标签会自动转换为短横线小写格式的 ID 和类名。转换过程包含以下步骤:移除所有非字母数字、下划线、连字符、长破折号或空格的字符,将剩余非字母数字字符替换为连字符(每组替换为一个),最后将结果转为全小写。
段落名称前会添加 passage-
前缀,根据使用场景不同转换为 ID 或类名:
#passage-gone-fishin
)<<include>>
宏),转换为类名(选择器示例:.passage-gone-fishin
)例如,段落名称为 Gone fishin'
时:
passage-gone-fishin
passage-gone-fishin
段落显示时,其标签会:
<html>
和 <body>
的 data-tags
属性(空格分隔列表)<body>
的类名(以下特殊标签除外):
Twine 2: | debug , nobr , passage , widget 及以 twine. 开头的标签 |
---|---|
Twine 1/Twee: | debug , nobr , passage , script , stylesheet , widget 及以 twine. 开头的标签 |
例如,标签 Sector_42
将转换为:
data-tags
属性值:Sector_42
(选择器:[data-tags~="Sector_42"]
)sector-42
(选择器:.sector-42
)选择器 | 描述 |
---|---|
html |
文档根元素,默认字体设置在此定义 活动段落的标签会添加到其 |
body |
页面主体,默认前景色和背景色设置在此 活动段落的标签会添加到其 |
#story |
故事主容器元素 |
#passages |
段落容器元素,所有段落元素都包含在此 |
.passage |
段落元素。通常每回合只有一个,但在导航时可能短暂存在两个(进入/离开段落) 活动段落名称会作为其 ID(详见段落转换规则) 活动段落的标签会添加到其 |
.passage a |
段落内所有链接元素 |
.passage a:hover |
段落内鼠标悬停的链接 |
.passage a:active |
段落内被点击的链接 |
.passage .link-broken |
指向不存在的段落的链接 |
.passage .link-disabled |
被禁用的链接(如已选择的 <<choice>> 宏链接) |
.passage .link-external |
外部链接(指向其他网站的链接) |
.passage .link-internal |
内部链接(指向段落或宏的链接) |
.passage .link-visited1 |
已访问过的内部链接(玩家历史中存在的段落) |
.passage .link-internal:not(.link-visited)1 |
未访问过的内部链接(玩家从未到达的段落) |
.link-visited
类默认未启用,详见 Config
API 的 Config.addVisitedLinkClass
属性在 Twine 1/Twee 中强烈建议只使用一个 stylesheet
标签的段落。由于 CSS 按加载顺序层叠,Twine 1/Twee 无法控制多个样式表的加载顺序,容易导致样式错乱。
SugarCube 不支持 Twine 1.4+ 原生的标签样式表功能。替代方案是使用属性选择器或类选择器:
例如,为带有 forest
标签的段落定义样式:
/* 在 <html> 使用属性选择器 */
html[data-tags~="forest"] { background-image: url(forest-bg.jpg); }
/* 在 <body> 使用属性选择器 */
body[data-tags~="forest"] .passage { color: darkgreen; }
/* 使用类选择器 */
body.forest a:hover { color: lime; }
以下是 SugarCube 内置样式表(按加载顺序排列,5-13 最常用),链接指向最新版本: 源代码仓库.
normalize.css
- 标准化浏览器默认样式init-screen.css
- 初始化加载界面font-icons.css
- 图标字体font-emoji.css
- Emoji 字体支持core.css
- 核心基础样式core-display.css
- 显示相关样式core-passage.css
- 段落容器样式core-macro.css
- 段落容器样式ui-dialog.css
- 对话框基础样式ui-dialog-saves.css
- 存档对话框样式ui-dialog-settings.css
- 设置对话框样式ui-dialog-legacy.css
- 旧版对话框兼容样式ui-bar.css
- 界面工具栏样式ui-debug-bar.css
- 调试工具栏样式(≥v2.23.0)ui-debug-views.css
- 调试视图样式(≤v2.22.0)The hierarchy of the document body, including associated HTML IDs and class names is as follows.
…
) signify data that is dynamically generated at run time.#story-title-separator
element is normally unused.#menu-story
, will only exist if the StoryMenu
special passage is used.#menu-item-continue
, will only exist if a browser save (auto or slot) exists.#menu-item-settings
, will only exist if the Setting
API is used.<body class="…">
<div id="init-screen"></div>
<div id="ui-overlay" class="ui-close"></div>
<div id="ui-dialog" tabindex="0" role="dialog" aria-labelledby="ui-dialog-title">
<div id="ui-dialog-titlebar">
<h1 id="ui-dialog-title"></h1>
<button id="ui-dialog-close" class="ui-close" tabindex="0" aria-label="…"></button>
</div>
<div id="ui-dialog-body"></div>
</div>
<div id="ui-bar">
<div id="ui-bar-tray">
<button id="ui-bar-toggle" tabindex="0" title="…" aria-label="…"></button>
<div id="ui-bar-history">
<button id="history-backward" tabindex="0" title="…" aria-label="…">…</button>
<button id="history-jumpto" tabindex="0" title="…" aria-label="…">…</button>
<button id="history-forward" tabindex="0" title="…" aria-label="…">…</button>
</div>
</div>
<div id="ui-bar-body">
<header id="title" role="banner">
<div id="story-banner"></div>
<h1 id="story-title"></h1>
<div id="story-subtitle"></div>
<div id="story-title-separator"></div>
<p id="story-author"></p>
</header>
<div id="story-caption"></div>
<nav id="menu" role="navigation">
<ul id="menu-story">…<ul>
<ul id="menu-core">
<li id="menu-item-continue"><a tabindex="0">…</a></li>
<li id="menu-item-saves"><a tabindex="0">…</a></li>
<li id="menu-item-settings"><a tabindex="0">…</a></li>
<li id="menu-item-restart"><a tabindex="0">…</a></li>
</ul>
</nav>
</div>
</div>
<div id="story" role="main">
<div id="passages">
<div class="passage …" id="…" data-passage="…">
<!-- The active (present) passage content -->
</div>
</div>
</div>
<!-- The story data chunk, which depends on the compiler release (see below) -->
<script id="script-sugarcube" type="text/javascript"><!-- The main SugarCube module --></script>
</body>
Periods of ellipsis (…
) signify data that is generated at compile time.
<tw-storydata name="…" startnode="…" creator="…" creator-version="…"
ifid="…" zoom="…" format="…" format-version="…" options="…" hidden>
<!-- Passage data nodes… -->
</tw-storydata>
<div id="store-area" data-size="…" hidden>
<!-- Passage data nodes… -->
</div>
事件(Events)是用于通知代码某些行为发生的消息机制(例如:触发、广播),涵盖从玩家交互到自动化流程的各种场景。每个事件对象都包含特定属性,可用于获取事件相关的附加信息。
本章节列出了 SugarCube 在故事运行过程中触发的各类专属事件。
参考阅读: 关于标准浏览器/DOM事件,请查阅 MDN 的事件参考手册。
对话框事件允许在对话框打开和关闭的特定时刻执行 JavaScript 代码。
参见:
Dialog
API。
:dialogclosed
事件 全局事件,在调用 Dialog.close()
方法时,作为关闭对话框的最后一步触发。
警告:
当使用 :dialogclosed
事件时,你无法从已关闭的对话框中获取数据(例如标题或类名),因为事件触发时对话框已经关闭并重置。如需获取此类信息,请改用 :dialogclosing
事件。
v2.29.0
:首次引入。注意:
虽然没有自定义属性,但事件是从对话框的 body 元素触发的,因此 target
属性会指向其 body 元素 (即 #ui-dialog-body
)。
/* 当事件触发时执行处理函数 */
$(document).on(':dialogclosed', (ev) => {
/* JavaScript 代码 */
});
/* 仅执行一次处理函数 */
$(document).one(':dialogclosed', (ev) => {
/* JavaScript 代码 */
});
:dialogclosing
事件 全局事件,在调用 Dialog.close()
方法时,作为关闭对话框的第一步触发。
v2.29.0
:首次引入。注意:
虽然没有自定义属性,但事件是从对话框的 body 元素触发的,因此 target
属性会指向其 body 元素 (即 #ui-dialog-body
)。
/* 当对话框开始关闭时执行处理函数(持续监听) */
$(document).on(':dialogclosing', (ev) => {
/* JavaScript 代码 */
});
/* 当对话框开始关闭时执行处理函数(仅执行一次) */
$(document).one(':dialogclosing', (ev) => {
/* JavaScript 代码 */
});
:dialogopened
事件 全局事件,在调用 Dialog.open()
方法时,作为打开对话框的最后一步触发。
v2.29.0
:首次引入。注意:
虽然没有自定义属性,但事件是从对话框的 body 元素触发的,因此 target
属性会指向其 body 元素 (即 #ui-dialog-body
)。
/* 当事件触发时执行处理函数 */
$(document).on(':dialogopened', (ev) => {
/* JavaScript 代码 */
});
/* 仅执行一次处理函数 */
$(document).one(':dialogopened', (ev) => {
/* JavaScript 代码 */
});
:dialogopening
事件 全局事件,在调用 Dialog.open()
方法时,作为打开对话框的第一步触发。
v2.29.0
:首次引入。注意:
虽然没有自定义属性,但事件是从对话框的 body 元素触发的,因此 target
属性会指向其 body 元素 (即 #ui-dialog-body
)。
/* 当事件触发时执行处理函数 */
$(document).on(':dialogopening', (ev) => {
/* JavaScript 代码 */
});
/* 仅执行一次处理函数 */
$(document).one(':dialogopening', (ev) => {
/* JavaScript 代码 */
});
导航事件允许在段落跳转的不同阶段执行 JavaScript 代码。
完整处理顺序如下(包含 :uiupdate
事件和各类特殊段落):
段落初始化
发生在状态历史修改之前。
:passageinit
事件段落启动
发生在新段落渲染之前。
PassageReady
特殊段落:passagestart
事件PassageHeader
特殊段落段落渲染
发生在新段落渲染完成后。
PassageFooter
特殊段落:passagerender
事件段落显示
发生在新段落内容输出到页面后。
PassageDone
特殊段落:passagedisplay
事件界面更新
发生在导航结束前。
:uiupdate
事件
StoryDisplayTitle
特殊段落StoryBanner
特殊段落StorySubtitle
特殊段落StoryAuthor
特殊段落StoryCaption
特殊段落StoryMenu
特殊段落段落结束
发生在导航流程完全结束时。
:passageend
事件:passageinit
事件 在状态记录修改前触发。
v2.20.0
:首次引入。v2.37.0
:将自定义属性移至事件的 detail
对象中。detail
对象属性::passageinit
事件包含具有以下属性的 detail
属性:
passage
: (Passage) 即将进入的段落对象。更多信息请参阅 Passage
API。/* 每次事件触发时执行处理函数 */
$(document).on(':passageinit', (ev) => {
/* 记录当前时刻的详细信息 */
console.group('当前时刻的详细信息');
console.log('段落名称:', ev.detail.passage.name);
console.log('段落标签:', ev.detail.passage.tags);
console.groupEnd();
/* 在此执行有效操作 */
});
/* 仅执行一次处理函数 */
$(document).one(':passageinit', (ev) => {
/* 在此执行有效操作 */
});
:passagestart
event Triggered before the rendering of the incoming passage.
v2.20.0
: Introduced.v2.37.0
: Moved custom properties into the event's detail
object.detail
object properties::passagestart
events have a detail
property whose value is an object with the following properties:
content
: (HTMLElement
) The, currently, empty element that will eventually hold the rendered content of the incoming passage.passage
: (Passage
) The incoming passage object. See the Passage
API for more information./* Execute the handler function each time the event triggers. */
$(document).on(':passagestart', (ev) => {
/* Log details about the current moment. */
console.group('Details about the current moment');
console.log('buffer:', ev.detail.content);
console.log('passage name:', ev.detail.passage.name);
console.log('passage tags:', ev.detail.passage.tags);
console.groupEnd();
/* Do something useful here. */
});
/* Execute the handler function exactly once. */
$(document).one(':passagestart', (ev) => {
/* Do something useful here. */
});
/*
Process the given markup and append the result to the incoming
passage's element.
*/
$(document).on(':passagestart', (ev) => {
$(ev.detail.content).wiki("In the //beginning//.");
});
:passagerender
event Triggered after the rendering of the incoming passage.
v2.20.0
: Introduced.v2.37.0
: Moved custom properties into the event's detail
object.detail
object properties::passagerender
events have a detail
property whose value is an object with the following properties:
content
: (HTMLElement
) The element holding the fully rendered content of the incoming passage.passage
: (Passage
) The incoming passage object. See the Passage
API for more information./* Execute the handler function each time the event triggers. */
$(document).on(':passagerender', (ev) => {
/* Log details about the current moment. */
console.group('Details about the current moment');
console.log('buffer:', ev.detail.content);
console.log('passage name:', ev.detail.passage.name);
console.log('passage tags:', ev.detail.passage.tags);
console.groupEnd();
/* Do something useful here. */
});
/* Execute the handler function exactly once. */
$(document).one(':passagerender', (ev) => {
/* Do something useful here. */
});
/*
Process the given markup and append the result to the incoming
passage's element.
*/
$(document).on(':passagerender', (ev) => {
$(ev.detail.content).wiki("At the //end// of some renderings.");
});
:passagedisplay
event Triggered after the display—i.e., output—of the incoming passage.
v2.20.0
: Introduced.v2.31.0
: Added content
property to event object.v2.37.0
: Moved custom properties into the event's detail
object.detail
object properties::passagedisplay
events have a detail
property whose value is an object with the following properties:
content
: (HTMLElement
) The element holding the fully rendered content of the incoming passage.passage
: (Passage
) The incoming passage object. See the Passage
API for more information./* Execute the handler function each time the event triggers. */
$(document).on(':passagedisplay', (ev) => {
/* Log details about the current moment. */
console.group('Details about the current moment');
console.log('buffer:', ev.detail.content);
console.log('passage name:', ev.detail.passage.name);
console.log('passage tags:', ev.detail.passage.tags);
console.groupEnd();
/* Do something useful here. */
});
/* Execute the handler function exactly once. */
$(document).one(':passagedisplay', (ev) => {
/* Do something useful here. */
});
/*
Process the given markup and append the result to the incoming
passage's element.
*/
$(document).on(':passagedisplay', (ev) => {
$(ev.detail.content).wiki("It's //showtime//!");
});
:passageend
event Triggered at the end of passage navigation.
v2.20.0
: Introduced.v2.31.0
: Added content
property to event object.v2.37.0
: Moved custom properties into the event's detail
object.detail
object properties::passageend
events have a detail
property whose value is an object with the following properties:
content
: (HTMLElement
) The element holding the fully rendered content of the incoming passage.passage
: (Passage
) The incoming passage object. See the Passage
API for more information./* Execute the handler function each time the event triggers. */
$(document).on(':passageend', (ev) => {
/* Log details about the current moment. */
console.group('Details about the current moment');
console.log('buffer:', ev.detail.content);
console.log('passage name:', ev.detail.passage.name);
console.log('passage tags:', ev.detail.passage.tags);
console.groupEnd();
/* Do something useful here. */
});
/* Execute the handler function exactly once. */
$(document).one(':passageend', (ev) => {
/* Do something useful here. */
});
/*
Process the given markup and append the result to the incoming
passage's element.
*/
$(document).on(':passageend', (ev) => {
$(ev.detail.content).wiki("So long and //thanks for all the fish//!");
});
prehistory
tasks Deprecated:
prehistory
tasks have been deprecated and should no longer be used. See the :passageinit
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.predisplay
tasks Deprecated:
predisplay
tasks have been deprecated and should no longer be used. See the :passagestart
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.prerender
tasks Deprecated:
prerender
tasks have been deprecated and should no longer be used. See the :passagestart
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.postrender
tasks Deprecated:
postrender
tasks have been deprecated and should no longer be used. See the :passagerender
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.postdisplay
tasks Deprecated:
postdisplay
tasks have been deprecated and should no longer be used. See the :passagedisplay
event for its replacement.
v2.0.0
: Introduced.v2.31.0
: Deprecated.SimpleAudio
Events SimpleAudio
events allow the execution of JavaScript code at specific points during audio playback.
SimpleAudio
API see:
:faded
event Track event triggered when a fade completes normally.
v2.29.0
: Introduced./* Execute the handler function when the event triggers for one track via <AudioTrack>. */
aTrack.on(':faded', (ev) => {
/* JavaScript code */
});
/* Execute the handler function when the event triggers for multiple tracks via <AudioRunner>. */
someTracks.on(':faded', (ev) => {
/* JavaScript code */
});
:fading
event Track event triggered when a fade starts.
v2.29.0
: Introduced./* Execute the handler function when the event triggers for one track via <AudioTrack>. */
aTrack.on(':fading', (ev) => {
/* JavaScript code */
});
/* Execute the handler function when the event triggers for multiple tracks via <AudioRunner>. */
someTracks.on(':fading', (ev) => {
/* JavaScript code */
});
:stopped
event Track event triggered when playback is stopped after <AudioTrack>.stop()
or <AudioRunner>.stop()
is called—either manually or as part of another process.
See Also:
ended
and pause
for information on somewhat similar native events.
v2.29.0
: Introduced./* Execute the handler function when the event triggers for one track via <AudioTrack>. */
aTrack.on(':stopped', (ev) => {
/* JavaScript code */
});
/* Execute the handler function when the event triggers for multiple tracks via <AudioRunner>. */
someTracks.on(':stopped', (ev) => {
/* JavaScript code */
});
System events allow the execution of JavaScript code at specific points during story startup and teardown.
:enginerestart
event Global event triggered once just before the page is reloaded when Engine.restart()
is called.
v2.23.0
: Introduced./* Execute the handler function when the event triggers. */
$(document).one(':enginerestart', (ev) => {
/* JavaScript code */
});
:storyready
event Global event triggered once just before the dismissal of the loading screen at startup.
v2.31.0
: Introduced./* Execute the handler function exactly once, since it's only fired once. */
$(document).one(':storyready', (ev) => {
/* JavaScript code */
});
:uiupdate
event Global event triggered when the built-in user interface is being updated when UI.update()
is called.
v2.37.0
: Introduced./* Execute the handler function each time the event triggers. */
$(document).on(':uiupdate', (ev) => {
/* JavaScript code */
});
/* Execute the handler function exactly once. */
$(document).one(':uiupdate', (ev) => {
/* JavaScript code */
});
<<type>>
Events <<type>>
macro events allow the execution of JavaScript code at specific points during typing.
:typingcomplete
event Global event triggered when all <<type>>
macros within a passage have completed.
Note:
Injecting additional <<type>>
macro invocations after a :typingcomplete
event has been fired will cause another event to eventually be generated, since you're creating a new sequence of typing.
v2.32.0
: Introduced./* Execute the handler function when the event triggers. */
$(document).on(':typingcomplete', (ev) => {
/* JavaScript code */
});
:typingstart
event Local event triggered on the typing wrapper when the typing of a section starts.
v2.32.0
: Introducedv2.33.0
: Changed to a local event that bubbles up the DOM tree./* Execute the handler function when the event triggers. */
$(document).on(':typingstart', (ev) => {
/* JavaScript code */
});
:typingstop
event Local event triggered on the typing wrapper when the typing of a section stops.
v2.32.0
: Introducedv2.33.0
: Changed to a local event that bubbles up the DOM tree./* Execute the handler function when the event triggers. */
$(document).on(':typingstop', (ev) => {
/* JavaScript code */
});
Config
API The Config
object controls various aspects of SugarCube's behavior.
Note:
Config
object settings should be placed within your project's JavaScript section (Twine 2: the Story JavaScript; Twine 1/Twee: a script
-tagged passage).
Config.addVisitedLinkClass
↔ boolean
(default: false
) Determines whether the link-visited
class is added to internal passage links that go to previously visited passages—i.e., the passage already exists within the story history.
Note:
You must provide your own styling for the link-visited
class as none is provided by default.
v2.0.0
: Introduced.A boolean
value signifying whether links to visited passages have the class added.
Config.addVisitedLinkClass = true;
You will also need to specify a .link-visited
style that defines the properties visited links should have. For example:
.link-visited {
color: purple;
}
Config.cleanupWikifierOutput
↔ boolean
(default: false
) Determines whether the output of the Wikifier is post-processed into more sane markup—i.e., where appropriate, it tries to transition the plethora of <br>
elements into <p>
elements.
v2.0.0
: Introduced.A boolean
value signifying whether post-processing occurs.
Config.cleanupWikifierOutput = true;
Config.debug
↔ boolean
(default: false
) Indicates whether SugarCube is running in test mode, which enables debug views and various optional debugging errors and warnings. See the Test Mode guide for more information.
Note:
This setting is automatically set based on whether you're using a testing mode in a Twine compiler—i.e., Test mode in Twine 2, Test Play From Here in Twine 1, or the test mode option (-t
, --test
) in Tweego. You may, however, forcibly enable it if you need to for some reason—e.g., if you're using another compiler, which doesn't offer a way to enable test mode.
See Also:
Config.enableOptionalDebugging
setting.
v2.2.0
: Introduced.A boolean
value signifying whether SugarCube is running in test mode.
// Forcibly enable test mode.
Config.debug = true;
if (Config.debug) {
// Do something debug related.
}
<<if Config.debug>>
/* do something debug related */
<</if>>
Config.enableOptionalDebugging
↔ boolean
(default: false
) Determines whether various optional debugging errors and warnings are enabled outside of test mode.
See Also:
Config.debug
setting.
List of optional errors and warnings: (not exhaustive)
<<if>>
macro assignment error. If enabled, returns an error when the =
assignment operator is used within its conditional—e.g., <<if $suspect = "Bob">>
. Does not flag other assignment operators.v2.37.0
: Introduced.A boolean
value signifying whether optional debugging errors and warnings are enabled.
Config.enableOptionalDebugging = true;
Config.loadDelay
↔ integer number
(default: 0
) Sets the integer delay (in milliseconds) before the loading screen is dismissed, once the document has signaled its readiness. Not generally necessary, however, some browsers render slower than others and may need a little extra time to get a media-heavy page done. This allows you to fine tune for those cases.
v2.0.0
: Introduced.An integer number
value denoting the delay (in milliseconds) before the loading screen is dismissed.
// Delay the dismissal of the loading screen by 2000ms (2s)
Config.loadDelay = 2000;
Config.audio.pauseOnFadeToZero
↔ boolean
(default: true
) Determines whether the audio subsystem automatically pauses tracks that have been faded to 0
volume (silent).
v2.28.0
: Introduced.A boolean
value signifying whether tracks that have faded to 0
are automatically paused.
Config.audio.pauseOnFadeToZero = false;
Config.audio.preloadMetadata
↔ boolean
(default: true
) Determines whether the audio subsystem attempts to preload track metadata—meaning information about the track (e.g., duration), not its audio frames.
Note: It is unlikely that you will ever want to disable this setting.
v2.28.0
: Introduced.A boolean
value signifying whether track metadata is preloaded.
Config.audio.preloadMetadata = false;
Config.history.controls
↔ boolean
(default: true
) Determines whether the story's history controls (Backward, Jump To, & Forward buttons) are enabled within the UI bar.
v2.0.0
: Introduced.A boolean
value signifying whether the story history controls are enabled.
Config.history.controls = false;
Config.history.maxStates
↔ integer number
(default: 40
) Sets the maximum number of states (moments) to which the history is allowed to grow. Should the history exceed the limit, states will be dropped from the past (oldest first).
Tip:
For game-oriented projects, as opposed to more story-oriented interactive fiction, a setting of 1
is strongly recommended.
v2.0.0
: Introduced.v2.36.0
: Reduced the default to 40
.An integer number
value denoting how many moments are allowed within the history.
// Limit the history to a single moment (recommended for games)
Config.history.maxStates = 1;
// Limit the history to 25 moments
Config.history.maxStates = 25;
Config.macros.maxLoopIterations
↔ integer number
(default: 1000
) Sets the maximum number of iterations allowed before the <<for>>
macro conditional forms are terminated with an error.
Note: This setting exists to prevent a misconfigured loop from making the browser unresponsive.
v2.0.0
: Introduced.An integer number
value denoting the total allowed number of conditional form loop iteration (per instance).
// Allow only 5000 iterations
Config.macros.maxLoopIterations = 5000;
Config.macros.typeSkipKey
↔ string
(default: " "
, space) Sets the default KeyboardEvent.key
value that causes the currently running <<type>>
macro instance to finish typing its content immediately.
v2.33.1
: Introduced.A string
value denoting the keyboard key that causes <<type>>
to immediately finish typing.
// Change the default skip key to Control (CTRL)
Config.macros.typeSkipKey = 'Control';
Config.macros.typeVisitedPassages
↔ boolean
(default: true
) Determines whether the <<type>>
macro types out content on previously visited passages or simply outputs it immediately.
v2.32.0
: Introduced.A boolean
value signifying whether <<type>>
types out previously visited passages.
// Do not type on previously visited passages
Config.macros.typeVisitedPassages = false;
Config.macros.ifAssignmentError
↔ boolean
(default: true
) Deprecated:
This setting has been deprecated and should no longer be used. See the Config.enableOptionalDebugging
setting for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Config.enableOptionalDebugging
setting.Config.navigation.override
↔ Function
(default: none) Allows the destination of passage navigation to be overridden. The callback is passed one parameter, the original destination passage name. If its return value is falsy, the override is cancelled and navigation to the original destination continues unperturbed. If its return value is truthy, the override succeeds and that value is used as the new destination of the navigation.
v2.13.0
: Introduced.The callback value (Function
).
destinationPassage
: (string
) The original destination passage name.Config.navigation.override = (destinationPassage) => {
/* code that returns a passage name or a falsy value */
};
Config.navigation.override = (destinationPassage) => {
var sv = State.variables;
// If $health is less-than-or-equal-to 0, go to the "You Died" passage instead.
if (sv.health <= 0) {
return 'You Died';
}
};
Config.passages.displayTitles
↔ boolean
(default: false
) Determines whether the current passage name is combined with the story's name within the browser tab's title bar.
v2.0.0
: Introduced.A boolean
value signifying whether passage names are combined with the story's name within the title bar.
Config.passages.displayTitles = true;
Config.passages.nobr
↔ boolean
(default: false
) Determines whether rendering passages have their leading/trailing newlines removed and all remaining sequences of newlines replaced with single spaces before they're rendered. Equivalent to including the nobr
special tag on every passage.
Note:
Does not affect script
or stylesheet
tagged passages, for Twine 1/Twee, or the Story JavaScript or Story Stylesheet sections, for Twine 2.
v2.19.0
: Introduced.A boolean
value signifying whether rendering passages have a global no-break enabled.
Config.passages.nobr = true;
Config.passages.onProcess
↔ Function
(default: none) Allows custom processing of passage text. The function is invoked each time the <Passage>.processText()
method is called. It is passed an abbreviated version of the associated passage's Passage
instance—containing only the name
, tags
, and text
properties. Its return value should be the post-processed text.
Note:
Does not affect script
or stylesheet
tagged passages, for Twine 1/Twee, or the Story JavaScript or Story Stylesheet sections, for Twine 2.
The function will be called just before the built-in no-break passage processing if you're also using that.
See the Config.passages.nobr
setting and nobr
special tag.
v2.30.0
: Introduced.A callback value (Function
).
passage
: (Object
) An abbreviated version of the associated passage's Passage
instance—containing only the name
, tags
, and text
properties.// Change instances of "cat" to "dog" in passages
Config.passages.onProcess = (p) => p.text.replace(/\bcat(s?)\b/g, 'dog$1');
Config.passages.start
↔ string
(Twine 2 default: user-selected; Twine 1/Twee default: "Start"
) Sets the starting passage name, the very first passage that will be displayed.
v2.0.0
: Introduced.A string
value denoting the starting passage name.
Config.passages.start = 'That Other Starting Passage';
Config.passages.transitionOut
↔ string
| integer number
(default: none) Determines whether outgoing passage transitions are enabled. Valid values are the name of the property being animated, which causes the outgoing passage element to be removed once that transition animation is complete, or an integer delay (in milliseconds), which causes the outgoing passage element to be removed once the delay has expired. You will also need some CSS styles to make this work—examples given below.
Note: If using an integer delay, ideally, it should probably be slightly longer than the outgoing transition delay that you intend to use—e.g., an additional 25ms or so should be sufficient.
v2.0.0
: Introduced.A string
value denoting the CSS property being animated or an integer number
value denoting the integer delay.
// Remove outgoing elements when their opacity animation ends
Config.passages.transitionOut = 'opacity';
// Remove outgoing elements after 1500ms (1.5s)
Config.passages.transitionOut = 1500;
At the very least you will need to specify a .passage-out
style that defines the transition's end state. For example:
.passage-out {
opacity: 0;
}
That probably won't be very pleasing to the eye, however, so you will likely need several styles to make something that looks half-decent. For example, the following will give you a basic crossfade:
#passages {
position: relative;
}
.passage {
left: 0;
position: absolute;
top: 0;
transition: opacity 1s ease;
}
.passage-out {
opacity: 0;
}
Config.passages.descriptions
↔ boolean
| Object
| Function
(default: none) Deprecated:
This setting has been deprecated and should no longer be used. See the Config.saves.descriptions
setting for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Config.saves.descriptions
setting.Config.saves.descriptions
↔ Function
(default: none) Sets browser saves descriptions. If unset, a brief description of the current turn is used. If a callback function is assigned, it is passed one parameter, the type of save being attempted. If its return value is truthy, the returned description is used, elsewise the default description is used.
See:
Save.Type
pseudo-enumeration for more information on save types.
v2.37.0
: Introduced.A callback value (Function
).
saveType
: (Save.Type
) The Save.Type
pseudo-enumeration. Used to denote the type of save.Config.saves.descriptions = (saveType) => passage();
let saveDescriptions = {
'passage_name_a' : 'description text a…',
'passage_name_b' : 'description text b…',
'passage_name_c' : 'description text c…'
};
Config.saves.descriptions = (saveType) => saveDescriptions[passage()];
saveType
parameterConfig.saves.descriptions = (saveType) => {
const base = `(${L10n.get('turn')} ${State.turns})`;
switch (saveType) {
case Save.Type.Auto:
return `${base} A browser auto save…`;
case Save.Type.Base64:
return `${base} A base64 save…`;
case Save.Type.Disk:
return `${base} A local disk save…`;
case Save.Type.Slot:
return `${base} A browser slot save…`;
}
};
Config.saves.id
↔ string
(default: slugified story name) Sets the ID associated with saves.
v2.0.0
: Introduced.A string
value denoting the ID associated with saves.
Config.saves.id = 'a-big-huge-story-part-1';
Config.saves.isAllowed
↔ Function
(default: none) Determines whether saving is allowed within the current context. If unset, saves are always allowed. If a callback function is assigned, it is passed one parameter, the type of save being attempted. If its return value is truthy, the save is allowed, elsewise it is disallowed.
See:
Save.Type
pseudo-enumeration for more information on save types.
v2.0.0
: Introduced.v2.37.0
: Added save type parameter.A callback value (Function
).
saveType
: (Save.Type
) The Save.Type
pseudo-enumeration. Used to denote the type of save.Allows saves on passages if it returns a truthy value.
Config.saves.isAllowed = (saveType) => {
/* code returning a boolean value */
};
Disallow saving on passages tagged with menu
.
Config.saves.isAllowed = (saveType) => !tags().includes('menu');
saveType
parameterAttempt a new auto save only on passages tagged with autosave
. Other save types are not limited.
Config.saves.isAllowed = (saveType) => {
if (saveType === Save.Type.Auto) {
return tags().includes('autosave');
}
return true;
};
Attempt a new auto save only on every eighth turn and limit all other save types to passages tagged with cansave
.
Config.saves.isAllowed = (saveType) => {
if (saveType === Save.Type.Auto) {
return turns() % 8 === 0;
}
return tags().includes('cansave');
};
Different logic for most save types.
Note: For example purposes only, not really recommended.
Config.saves.isAllowed = (saveType) => {
switch (saveType) {
case Save.Type.Auto:
// Only every tenth turn
return turns() % 10 === 0;
case Save.Type.Disk:
case Save.Type.Slot:
// Only on passages tagged `cansave`
return tags().includes('cansave');
case Save.Type.Base64:
// Always
return true;
}
};
Config.saves.maxAutoSaves
↔ integer number
(default: 0
) Sets the maximum number of available auto saves. Using a value of 0
disables auto saves.
Note:
When enabled, an auto save is attempted each turn by default. Thus, it is recommended that the Config.saves.isAllowed
setting be used to limit the frequency.
Warning:
As available browser-based storage is very limited, it is strongly recommended that the number of available saves not be set too high. A range of 1
–10
is suggested.
v2.37.0
: Introduced.An integer number
value denoting the maximum number of browser auto saves.
Config.saves.maxAutoSaves = 3;
Config.saves.maxSlotSaves
↔ integer number
(default: 8
) Sets the maximum number of available slot saves. Using a value of 0
disables slot saves.
Warning:
As available browser-based storage is very limited, it is strongly recommended that the number of available saves not be set too high. A range of 1
–10
is suggested.
v2.37.0
: Introduced.An integer number
value denoting the maximum number of browser slot saves.
Config.saves.maxSlotSaves = 4;
Config.saves.metadata
↔ Function
(default: none) Sets the metadata
property of saves. The callback is invoked each time a save is made. It is passed the type of save being attempted. Its return value should be an Object
that will serve as the metadata of saves.
See:
Save.Type
pseudo-enumeration for more information on save types.
v2.37.0
: Introduced.A callback value (Function
). The callback is passed one parameter, the type of save being attempted. It should return an Object
that will serve as the metadata of saves and that must be JSON-serializable.
saveType
: (Save.Type
) The Save.Type
pseudo-enumeration. Used to denote the type of save.Config.saves.metadata = (saveType) => {
const sv = State.variables;
return {
party : sv.party, // e.g., ['Celes', 'Locke', 'Edward']
gold : sv.gold // e.g., 2345
};
};
Config.saves.metadata = (saveType) => {
const metadata = {
gameVersion : 'Release 12: "Horny Toads Need Love Too" edition'
};
if (/* some logic */) {
metadata.someProp = 'a value';
}
return metadata;
};
saveType
parameterConfig.saves.isAllowed = (saveType) => {
const metadata = {
gameVersion : 'Release 2: Electric Boogaloo',
inBrowser : saveType === Save.Type.Auto || saveType === Save.Type.Slot
};
return metadata;
};
Config.saves.version
↔ any
(default: none) Sets the version
property of saves.
Note:
It is strongly recommended that you use an integer number
for the version.
Note:
This setting is only used to set the version
property of saves. Thus, it is only truly useful if you plan to upgrade out-of-date saves via the Save
Events API—specifically the Save.onLoad.add()
static method.
v2.0.0
: Introduced.An any
value denoting the version of saves.
// As an integer (strongly recommended)
Config.saves.version = 3;
// As a string (strongly NOT recommended)
Config.saves.version = "v3";
Config.saves.version = "1.3.12";
Config.saves.autoload
↔ boolean
| string
| Function
(default: none) Deprecated:
This setting has been deprecated and should no longer be used. The default UI now includes a Continue button, which loads the latest save. If disabling or replacing the default UI, see the Save.browser.continue()
method to replicate the functionality.
v2.0.0
: Introduced.v2.37.0
: Deprecated.Config.saves.autosave
↔ boolean
| Array<string>
| Function
(default: none) Deprecated:
This setting has been deprecated and should no longer be used. See the Config.saves.maxAutoSaves
setting to set the number of available auto saves and the Config.saves.isAllowed
setting to control when new auto saves are created.
v2.0.0
: Introduced.v2.30.0
: Added function values and deprecated string values.v2.37.0
: Deprecated.Config.saves.onLoad
↔ Function
(default: none) Deprecated:
This setting has been deprecated and should no longer be used. See the Save.onLoad.add()
method for its replacement.
v2.0.0
: Introduced.v2.36.0
: Deprecated in favor of the Save
Events API.Config.saves.onSave
↔ Function
(default: none) Deprecated:
This setting has been deprecated and should no longer be used. See the Save.onSave.add()
method for its replacement.
v2.0.0
: Introduced.v2.33.0
: Added save operation details object parameter to the callback function.v2.36.0
: Deprecated in favor of the Save
Events API.Config.saves.slots
↔ integer number
(default: 8
) Deprecated:
This setting has been deprecated and should no longer be used. See the Config.saves.maxSlotSaves
setting for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Config.saves.maxSlotSaves
setting.Config.saves.tryDiskOnMobile
↔ boolean
(default: true
) Deprecated: This setting has been deprecated and should no longer be used. Saving to disk on mobile devices is now unconditionally enabled.
v2.34.0
: Introduced.v2.37.0
: Deprecated.Config.ui.stowBarInitially
↔ boolean
| integer number
(default: 800
) Determines whether the UI bar (sidebar) starts in the stowed (shut) state initially. Valid values are boolean true
/false
, which causes the UI bar to always/never start in the stowed state, or an integer, which causes the UI bar to start in the stowed state if the viewport width is less-than-or-equal-to the specified number of pixels.
v2.11.0
: Introduced.A boolean
value signifying whether the UI bar starts stowed initially or an integer number
whose value denotes the width in pixels that the viewport has to be less-than-or-equal-to for the UI bar to start stowed initially.
// As a boolean; always start stowed
Config.ui.stowBarInitially = true;
// As a boolean; never start stowed
Config.ui.stowBarInitially = false;
// As an integer; start stowed if the viewport is 800px or less
Config.ui.stowBarInitially = 800;
Config.ui.updateStoryElements
↔ boolean
(default: true
) Determines whether certain elements within the UI bar are updated upon passage navigation. The affected elements are (in order): StoryDisplayTitle
, StoryBanner
, StorySubtitle
, StoryAuthor
, StoryCaption
, and StoryMenu
.
Note:
The StoryTitle
passage is not included in updates because SugarCube uses it as the basis for the key used to store and load data used when playing the story and for saves. If you need a dynamic title, then that's the purpose of StoryDisplayTitle
.
v2.0.0
: Introduced.A boolean
value signifying whether certain UI bar elements are updated upon navigation.
// If you don't need those elements to update
Config.ui.updateStoryElements = false;
Dialog
API Dialog.append(content)
→ Dialog
Appends the given content to the dialog's content area. Returns a reference to the Dialog
object for chaining.
Warning:
If your content contains any SugarCube markup, you'll need to use the Dialog.wiki()
method instead.
v2.9.0
: Introduced.content
: (Node
| string
) The content to append. As this method is essentially a shortcut for jQuery(Dialog.body()).append(…)
, see jQuery's append()
method for the range of valid content types.The Dialog
object for chaining.
Dialog.append("Cry 'Havoc!', and let slip the <em>dogs</em> of <strong>war</strong>.");
Dialog.append( /* DOM nodes */ );
Dialog.body()
→ HTMLElement
Returns a reference to the dialog's content area.
Note:
In practice, this method should usually be unnecessary as the Dialog.append()
method exists.
v2.0.0
: Introduced.The dialog's body element (HTMLElement
).
jQuery(Dialog.body())
.append("Cry 'Havoc!', and let slip the <em>dogs</em> of <strong>war</strong>.");
jQuery(Dialog.body())
.wiki("Cry 'Havoc!', and let slip the //dogs// of ''war''.");
Dialog.close()
→ Dialog
Closes the dialog. Returns a reference to the Dialog
object for chaining.
v2.0.0
: Introduced.The Dialog
object for chaining.
Dialog.close();
Dialog.create([title [, classNames]])
→ Dialog
Prepares the dialog for use. Returns a reference to the Dialog
object for chaining.
v2.37.0
: Introduced.title
: (optional, string
) The title of the dialog.classNames
: (optional, string
) The space-separated-list of classes to add to the dialog.The Dialog
object for chaining.
Dialog.create();
title
parameterDialog.create('Character Sheet');
classNames
parameterDialog.create(null, 'charsheet');
Dialog.create('Character Sheet', 'charsheet');
Dialog
.create('Character Sheet', 'charsheet')
.wikiPassage('Player Character')
.open();
Dialog.empty()
→ Dialog
Empties the dialog's content area. Returns a reference to the Dialog
object for chaining.
v2.37.0
: Introduced.The Dialog
object for chaining.
Dialog.empty();
Dialog
.empty()
.wikiPassage('Quests');
Dialog.isOpen([classNames])
→ boolean
Returns whether the dialog is currently open.
v2.0.0
: Introduced.classNames
: (optional, string
) The space-separated-list of classes to check for when determining the state of the dialog. Each of the built-in dialogs contains a name-themed class that can be tested for in this manner—e.g., the Saves dialog contains the class saves
.A boolean
denoting whether the dialog is currently open.
if (Dialog.isOpen()) {
/* code to execute if any dialog is open… */
}
if (!Dialog.isOpen()) {
/* code to execute if no dialog is open… */
}
classNames
parameterif (Dialog.isOpen('saves')) {
/* code to execute if the Saves dialog is open… */
}
if (!Dialog.isOpen('saves')) {
/*
code to execute if either no dialog or the Saves dialog,
specifically, is not open…
*/
}
Dialog.open([options [, closeFn]])
→ Dialog
Opens the dialog. Returns a reference to the Dialog
object for chaining.
Note: Call this only after populating the dialog with content.
v2.0.0
: Introduced.options
: (optional, null
| Object
) The options to be used when opening the dialog.closeFn
: (optional, null
| Function
) The function to execute whenever the dialog is closed.An options object should have some of the following properties:
top
: Top y-coordinate of the dialog in pixels without the unit (default: 50
).The Dialog
object for chaining.
Dialog.open();
options
parameterDialog.open({ top : 100 });
closeFn
parameterDialog.open(null, () => {
/* code to execute on close… */
});
Dialog.open({ top : 100 }, () => {
/* code to execute on close… */
});
Dialog.wiki(wikiMarkup)
→ Dialog
Renders the given markup and appends it to the dialog's content area. Returns a reference to the Dialog
object for chaining.
Note:
If you simply want to render a passage, see the Dialog.wikiPassage()
method instead.
Warning:
If your content consists of DOM nodes, you'll need to use the Dialog.append()
method instead.
v2.9.0
: Introduced.wikiMarkup
: (string
) The markup to render.The Dialog
object for chaining.
Dialog.wiki("Cry 'Havoc!', and let slip the //dogs// of ''war''.");
Dialog.wikiPassage(passageName)
→ Dialog
Renders the passage by the given name and appends it to the dialog's content area. Returns a reference to the Dialog
object for chaining.
v2.37.0
: Introduced.passageName
: (string
) The name of the passage to render.The Dialog
object for chaining.
Dialog.wikiPassage('Inventory');
Dialog.setup([title [, classNames]])
→ HTMLElement
Deprecated: This method has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of Dialog.create()
.Engine
API Engine.State
Engine state pseudo-enumeration. Used to denote the state of the engine.
As passage navigation occurs the engine cycles through the states thusly: idle (start) → playing → rendering → playing → idle (end).
v2.0.0
: Introduced.v2.37.0
: Changed into a public API.State | Description |
---|---|
Engine.State.Idle |
The engine is currently idle, awaiting the triggering of passage navigation. This is the default state. |
Engine.State.Playing |
Passage navigation has been triggered and the engine is playing/processing a passage. |
Engine.State.Rendering |
The incoming passage is being rendered. This takes place during and implies Engine.State.Playing .
|
Engine.lastPlay
→ number
Returns a timestamp representing the last time Engine.play()
was called.
v2.0.0
: Introduced.A timestamp (integer number) representing the last time Engine.play()
was called.
Recording the timestamp for later use.
let lastPlay = Engine.lastPlay;
Using the timestamp to determine elapsed time.
if ((now() - Engine.lastPlay) > 5000) {
// 5000ms (5s) have elapsed since Engine.play() was last called
}
Engine.state
→ Engine.State
Returns the current state of the engine.
v2.7.0
: Introduced.An Engine.State
value.
// Returns the current state of the engine
Engine.state;
Engine.backward()
→ boolean
Moves backward one moment within the full history (past + future), if possible, activating and showing the moment moved to. Returns whether the history navigation was successful (should only fail if already at the beginning of the full history).
v2.0.0
: Introduced.A boolean
value denoting whether the history navigation was successful.
// Rewinds the full history by one moment—i.e., undoes the moment
Engine.backward();
Engine.forward()
→ boolean
Moves forward one moment within the full history (past + future), if possible, activating and showing the moment moved to. Returns whether the history navigation was successful (should only fail if already at the end of the full history).
v2.0.0
: Introduced.A boolean
value denoting whether the history navigation was successful.
// Forwards the full history by one moment—i.e., redoes the moment
Engine.forward();
Engine.go(offset)
→ boolean
Activates the moment at the given offset from the active (present) moment within the full state history and show it. Returns whether the history navigation was successful (should only fail if the offset from the active (present) moment is not within the bounds of the full history).
v2.0.0
: Introduced.offset
: (integer number
) The offset from the active (present) moment of the moment to go to.A boolean
value denoting whether the history navigation was successful.
// Forwards the full history by two moments—i.e., redoes the moments
Engine.go(2);
// Rewinds the full history by four moments—i.e., undoes the moments
Engine.go(-4);
Engine.goTo(index)
→ boolean
Activates the moment at the given index within the full state history and show it. Returns whether the history navigation was successful (should only fail if the index is not within the bounds of the full history).
v2.0.0
: Introduced.index
: (integer number
) The index of the moment to go to.A boolean
value denoting whether the history navigation was successful.
// Goes to the first moment
Engine.goTo(0);
// Goes to the tenth moment
Engine.goTo(9);
Engine.isIdle()
→ boolean
Returns whether the engine is idle.
v2.16.0
: Introduced.A boolean
value denoting whether the engine is idle.
if (Engine.isIdle()) {
// do something while idle…
}
if (!Engine.isIdle()) {
// do something while not idle…
}
Engine.isPlaying()
→ boolean
Returns whether the engine is processing a turn—i.e., passage navigation has been triggered.
v2.16.0
: Introduced.A boolean
value denoting whether the engine is playing.
if (Engine.isPlaying()) {
// do something while playing…
}
if (!Engine.isPlaying()) {
// do something while not playing…
}
Engine.isRendering()
→ boolean
Returns whether the engine is rendering the incoming passage.
v2.16.0
: Introduced.A boolean
value denoting whether the engine is rendering.
if (Engine.isRendering()) {
// do something while rendering…
}
if (!Engine.isRendering()) {
// do something while not rendering…
}
Engine.play(passageName [, noHistory])
→ HTMLElement
Renders and displays the passage referenced by the given name, optionally without adding a new moment to the history.
v2.0.0
: Introduced.passageName
: (string
) The name of the passage to play.noHistory
: (optional, boolean
) Disables the update of the history—i.e., no moment is added to the history.An HTMLElement
value.
// Renders, displays, and adds a new moment to the history for the passage named "Foo"
Engine.play('Foo');
// Renders and displays, but does not add a moment to the history for the passage named "Foo"
Engine.play('Foo', true);
Engine.restart()
Causes the browser to immediately attempt to reload the window, thus restarting the story.
Warning: The player will not be prompted and all unsaved state will be lost.
Note:
In general, you should not call this method directly. Instead, call the UI.restart()
static method, which prompts the player with an OK/Cancel dialog before itself calling Engine.restart()
, if they accept.
v2.0.0
: Introduced.Engine.restart();
Engine.show()
→ HTMLElement
Renders and displays the active (present) moment's associated passage without adding a new moment to the history.
v2.0.0
: Introduced.An HTMLElement
value.
// Renders and displays the present passage without adding a new moment to the history
Engine.show();
Fullscreen
API Provides access to browsers' fullscreen functionality.
If you wish to use custom backgrounds, either simply colors or with images, then you should place them on the body
element. For example:
body {
background: #111 fixed url("images/background.png") center / contain no-repeat;
}
Warning:
It is strongly recommended that you do not place background properties on the html
element in addition to the body
element as this can cause background jitter in Internet Explorer when scrolling outside of fullscreen mode.
Warning:
If setting a background image via the background
shorthand property, then you should also specify a background-color
value with it or include a separate background-color
property after the background
property. The reason being is that the background
property resets the background color, so if you do not set one either as one of its values or via a following background-color
property, then the browser's default background color could show through if the background image does not cover the entire viewport or includes transparency.
The Fullscreen
API comes with some built-in limitations:
Fullscreen.element
→ HTMLElement
| null
Returns the current fullscreen element or, if fullscreen mode is not active, null
.
v2.31.0
: Introduced.Fullscreen.element → The current fullscreen element
Fullscreen.isEnabled()
→ boolean
Returns whether fullscreen is both supported and enabled.
v2.31.0
: Introduced.Fullscreen.isEnabled() → Whether fullscreen mode is available
Fullscreen.isFullscreen()
→ boolean
Returns whether fullscreen mode is currently active.
v2.31.0
: Introduced.Fullscreen.isFullscreen() → Whether fullscreen mode is active
Fullscreen.request([options [, requestedEl]])
→ Promise
Request that the browser enter fullscreen mode.
See: Backgrounds and limitations.
v2.31.0
: Introduced.options
: (optional, Object
) The fullscreen options object.requestedEl
: (optional, HTMLElement
) The element to enter fullscreen mode with. If omitted, defaults to the entire page.A fullscreen options object should have some of the following properties:
navigationUI
: (string
) Determines the fullscreen navigation UI preference. Valid values are (default: "auto"
):
"auto"
: Indicates no preference."hide"
: Request that the browser's navigation UI be hidden. The full dimensions of the screen will be used to display the element."show"
: Request that the browser's navigation UI be shown. The screen dimensions allocated to the element will be clamped to leave room for the UI.Note:
Browsers are not currently required to honor the navigationUI
setting.
/* Request to enter fullscreen mode. */
Fullscreen.request();
/* Request to enter fullscreen mode while showing its navigation UI and with the given element. */
Fullscreen.request({ navigationUI : "show" }, myElement);
Fullscreen.exit()
→ Promise
Request that the browser exit fullscreen mode.
v2.31.0
: Introduced./* Request to exit fullscreen mode. */
Fullscreen.exit();
Fullscreen.toggle([options [, requestedEl]])
→ Promise
Request that the browser toggle fullscreen mode—i.e., enter or exit as appropriate.
v2.31.0
: Introduced.options
: (optional, Object
) The fullscreen options object. See Fullscreen.request()
for more information.requestedEl
: (optional, HTMLElement
) The element to toggle fullscreen mode with. If omitted, defaults to the entire page./* Request to toggle fullscreen mode. */
Fullscreen.toggle();
/* Request to toggle fullscreen mode while showing its navigation UI and with the given element. */
Fullscreen.toggle({ navigationUI : "show" }, myElement);
Fullscreen.onChange(handlerFn [, requestedEl])
Attaches fullscreen change event handlers.
v2.31.0
: Introduced.handlerFn
: (Function
) The function to invoke when fullscreen mode is changed.requestedEl
: (optional, HTMLElement
) The element to attach the handler to./* Attach a hander to listen for fullscreen change events. */
Fullscreen.onChange(function (ev) {
/* Fullscreen mode changed, so do something. */
});
/* Attach a hander to the given element to listen for fullscreen change events. */
Fullscreen.onChange(function (ev) {
/* Fullscreen mode changed on myElement, so do something. */
}, myElement);
Fullscreen.offChange([handlerFn [, requestedEl]])
Removes fullscreen change event handlers.
v2.31.0
: Introduced.handlerFn
: (optional, Function
) The function to remove. If omitted, will remove all handler functions.requestedEl
: (optional, HTMLElement
) The element to remove the handler(s) from./* Remove all fullscreen change event handers. */
Fullscreen.offChange();
/* Remove the given fullscreen change event hander. */
/* NOTE: Requires that the original handler function was saved. */
Fullscreen.offChange(originalHandlerFn);
/* Remove all fullscreen change event handers from myElement. */
Fullscreen.offChange(null, myElement);
/* Remove the given fullscreen change event hander from myElement. */
/* NOTE: Requires that the original handler function was saved. */
Fullscreen.offChange(originalHandlerFn, myElement);
Fullscreen.onError(handlerFn [, requestedEl])
Attaches fullscreen error event handlers.
v2.31.0
: Introduced.handlerFn
: (Function
) The function to invoke when fullscreen mode encounters an error.requestedEl
: (optional, HTMLElement
) The element to attach the handler to./* Attach a hander to listen for fullscreen error events. */
Fullscreen.onError(function (ev) {
/* Fullscreen mode changed, so do something. */
});
/* Attach a hander to the given element to listen for fullscreen error events. */
Fullscreen.onError(function (ev) {
/* Fullscreen mode changed on myElement, so do something. */
}, myElement);
Fullscreen.offError([handlerFn [, requestedEl]])
Removes fullscreen error event handlers.
v2.31.0
: Introduced.handlerFn
: (optional, Function
) The function to remove. If omitted, will remove all handler functions.requestedEl
: (optional, HTMLElement
) The element to remove the handler(s) from./* Remove all fullscreen error event handers. */
Fullscreen.offError();
/* Remove the given fullscreen error event hander. */
/* NOTE: Requires that the original handler function was saved. */
Fullscreen.offError(originalHandlerFn);
/* Remove all fullscreen error event handers from myElement. */
Fullscreen.offError(null, myElement);
/* Remove the given fullscreen error event hander from myElement. */
/* NOTE: Requires that the original handler function was saved. */
Fullscreen.offError(originalHandlerFn, myElement);
LoadScreen
API Note:
To simply add a delay to the dismissal of the loading screen to hide initial flashes of unstyled content (FOUC)—e.g., style changes and page reflows—you do not need to use this API. See the Config.loadDelay
configuration setting.
LoadScreen.lock()
→ integer number
Acquire a loading screen lock and, if necessary, display the loading screen.
v2.15.0
: Introduced.The (integer number
) lock ID.
See the LoadScreen.unlock()
static method for additional examples.
// Lock the loading screen and get the lock ID.
var lockId = LoadScreen.lock();
LoadScreen.unlock(lockId)
Release the loading screen lock with the given ID and, if no other locks exist, hide the loading screen.
v2.15.0
: Introduced.lockId
: (integer number
) The loading screen lock ID.// Lock the loading screen and get the lock ID.
var lockId = LoadScreen.lock();
// Do something whose timing is unpredictable that should be hidden by the loading screen.
// Release the given lock ID.
LoadScreen.unlock(lockId);
Macro
API See Also:
MacroContext
API.
Macro.add(name , definition)
Add new macro(s).
v2.0.0
: Introducedv2.33.0
: Obsoleted the deep
parameter.name
: (string
| Array<string>
) Name, or array of names, of the macro(s) to add. NOTE: Names must consist of characters from the basic Latin alphabet and start with a letter, which may be optionally followed by any number of letters, numbers, the underscore, or the hyphen.definition
: (Object
| string
) Definition of the macro(s) or the name of an existing macro whose definition to copy.A macro definition object should have some of the following properties (only handler
is absolutely required):
skipArgs
: (optional, boolean
| Array<string>
) Disables parsing argument strings into discrete arguments. Used by macros that only use the raw/full argument strings. Boolean true
to affect all tags or an array of tag names to affect.tags
: (optional, null
| Array<string>
) Signifies that the macro is a container macro—i.e., not self-closing. An array child tag names or null
, if there are no child tags.handler
: (Function
) The macro's main function. It will be called without arguments, but with its this
set to a macro context object.Additional properties may be added for internal use.
/*
Example of a very simple/naive <<if>>/<<elseif>>/<<else>> macro implementation.
*/
Macro.add('if', {
skipArgs : true,
tags : ['elseif', 'else'],
handler : function () {
try {
for (var i = 0, len = this.payload.length; i < len; ++i) {
if (
this.payload[i].name === 'else' ||
!!Scripting.evalJavaScript(this.payload[i].args.full)
) {
jQuery(this.output).wiki(this.payload[i].contents);
break;
}
}
}
catch (ex) {
return this.error('bad conditional expression: ' + ex.message);
}
}
});
Macro.delete(name)
Remove existing macro(s).
v2.0.0
: Introduced.name
: (string
| Array<string>
) Name, or array of names, of the macro(s) to remove.Macro.delete("amacro")
Macro.delete(["amacro", "bmacro"])
Macro.get(name)
→ Object
Return the named macro definition, or null
on failure.
v2.0.0
: Introduced.name
: (string
) Name of the macro whose definition should be returned.Macro.get("print")
Macro.has(name)
→ boolean
Returns whether the named macro exists.
v2.0.0
: Introduced.name
: (string
) Name of the macro to search for.Macro.has("print")
Macro.tags.get(name)
→ Array<string>
Return the named macro tag's parents array (includes the names of all macros who have registered the tag as a child), or null
on failure.
v2.0.0
: Introduced.name
: (string
) Name of the macro tag whose parents array should be returned.Macro.tags.get("else") → For the standard library, returns: ["if"]
Macro.tags.has(name)
→ boolean
Returns whether the named macro tag exists.
v2.0.0
: Introduced.name
: (string
) Name of the macro tag to search for.Macro.tags.has("else")
MacroContext
API See Also:
Macro
API.
Macro handlers are called with no arguments, but with their this
set to a macro (execution) context object. Macro context objects contain the following data and method properties.
<MacroContext>.args
→ Array<any>
An array of discrete arguments parsed from the argument string.
v2.0.0
: Introduced.The Array
of arguments.
// Given: <<someMacro "a" "b" "c">>
this.args.length // Returns 3
this.args[0] // Returns 'a'
this.args[1] // Returns 'b'
this.args[2] // Returns 'c'
<MacroContext>.args.full
→ string
The argument string after converting all TwineScript syntax elements into their native JavaScript counterparts.
v2.0.0
: Introduced.The processed argument string
.
// Given: <<if $a is "b">>
this.args.full // Returns 'State.variables.a === "b"'
<MacroContext>.args.raw
→ string
The unprocessed argument string.
v2.0.0
: Introduced.The unprocessed argument string
.
// Given: <<if "a" is "b">>
this.args.raw // Returns '"a" is "b"'
<MacroContext>.name
→ string
The name of the macro.
v2.0.0
: Introduced.The macro's name (string
).
// Given: <<someMacro …>>
this.name // Returns 'someMacro'
<MacroContext>.output
→ HTMLElement
The current output element.
v2.0.0
: Introduced.The output element (HTMLElement
).
jQuery
$(this.output).wiki('Some //awesome// markup!');
jQuery
new Wikifier(this.output, 'Some //awesome// markup!');
<MacroContext>.parent
→ Object
| null
The macro context object of the macro's parent, or null
if the macro has no parent.
v2.0.0
: Introduced.The macro context (Object
), elsewise null
.
<MacroContext>.parser
→ Wikifier
The parser instance that generated the macro call.
v2.0.0
: Introduced.The parser instance (Wikifier
).
<MacroContext>.payload
→ Array<Object>
| null
The text of a container macro parsed into discrete payload objects by tag. Payload objects have the following properties:
name
: (string
) Name of the current tag.args
: (Array<any>
) The current tag's argument string parsed into an array of discrete arguments. Equivalent in function to <MacroContext>.args
.
args.full
: (string
) The current tag's argument string after converting all TwineScript syntax elements into their native JavaScript counterparts. Equivalent in function to <MacroContext>.args.full
.args.raw
: (string
) The current tag's unprocessed argument string. Equivalent in function to <MacroContext>.args.raw
.contents
: (string
) The current tag's contents—i.e., the text between the current tag and the next.v2.0.0
: Introduced.The Array<Object>
containing payloads, elsewise null
.
<MacroContext>.self
→ Object
The macro's definition—created via Macro.add()
.
v2.0.0
: Introduced.The macro definition (Object
).
<MacroContext>.contextFilter(predicate)
→ Array<Object>
Returns a new array containing all of the macro's ancestors that passed the test implemented by the given predicate function or an empty array, if no members pass.
v2.37.0
: Introduced.predicate
: (Function
) The function used to test each ancestor execution context object, which is passed in as its sole parameter.An Array<Object>
containing the passing ancestors or an empty array, if none pass.
A TypeError
instance.
var isInclude = function (ctx) { return ctx.name === 'include'; };
this.contextFilter(isInclude); // Returns an array of all <<include>> macro ancestors
<MacroContext>.contextFind(predicate)
→ Object
| undefined
Returns the first of the macro's ancestors that passed the test implemented by the given predicate function or undefined
, if no members pass.
v2.37.0
: Introduced.predicate
: (Function
) The function used to test each ancestor execution context object, which is passed in as its sole parameter.An Object
or undefined
, if no ancestors pass.
A TypeError
instance.
var isInclude = function (ctx) { return ctx.name === 'include'; };
this.contextFind(isInclude); // Returns the first <<include>> macro ancestor
<MacroContext>.contextSome(predicate)
→ boolean
Returns whether any of the macro's ancestors passed the test implemented by the given predicate function.
v2.37.0
: Introduced.predicate
: (Function
) The function used to test each ancestor execution context object, which is passed in as its sole parameter.A Boolean
.
A TypeError
instance.
var isInclude = function (ctx) { return ctx.name === 'include'; };
this.contextSome(isInclude); // Returns true if any ancestor was an <<include>> macro
<MacroContext>.error(message)
→ boolean
Renders the message prefixed with the name of the macro and returns false
.
v2.0.0
: Introduced.message
: (string
) The error message to output.Boolean false
.
// Given: <<someMacro …>>
return this.error('oops'); // Outputs '<<someMacro>>: oops'
<MacroContext>.shadowHandler(callback [, doneCallback [, startCallback]])
→ Function
Returns a callback function that wraps the specified callback functions to provide access to the variable shadowing system used by the <<capture>>
macro.
Note:
All of the specified callbacks are invoked as the wrapper is invoked—meaning, with their this
set to the this
of the wrapper and with whatever parameters were passed to the wrapper.
Warning:
Only useful when you have an asynchronous callback that invokes code/content that needs to access story and/or temporary variables shadowed by <<capture>>
. If you don't know what that means, then this API is likely not for you.
v2.37.0
: Introduced.callback
: (Function
) The main callback function, executed when the wrapper is invoked. Receives access to variable shadows.doneCallback
: (optional, Function
) The finalization callback function, executed after the main callback
returns. Does not receive access to variable shadows.startCallback
: (optional, Function
) The initialization callback function, executed before the main callback
is invoked. Does not receive access to variable shadows.A Function
.
$someElement.on('some_event', this.shadowHandler(function (ev) {
/* main asynchronous code */
}));
doneCallback
$someElement.on('some_event', this.shadowHandler(
function (ev) {
/* main asynchronous code */
},
function (ev) {
/* asynchronous code invoked after the main code */
}
));
doneCallback
and startCallback
$someElement.on('some_event', this.shadowHandler(
function (ev) {
/* main asynchronous code */
},
function (ev) {
/* asynchronous code invoked after the main code */
},
function (ev) {
/* asynchronous code invoked before the main code */
}
));
<MacroContext>.wiki(sources…)
Wikifies the given content source(s) and appends the result to the macro's output.
v2.38.0
: Introduced.sources
: (string
…) The list of content sources.this.wiki('Who //are// you?'); // Outputs "Who <em>are</em> you?"
this.wiki(
'Goodnight…',
'sweet prince.'
); // Outputs "Goodnight…sweet prince."
<MacroContext>.contextHas(filter)
→ boolean
Deprecated:
This method has been deprecated and should no longer be used. See the <MacroContext>.contextSome()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of <MacroContext>.contextSome()
.<MacroContext>.contextSelect(filter)
→ Object
| null
Deprecated:
This method has been deprecated and should no longer be used. See the <MacroContext>.contextFind()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of <MacroContext>.contextFind()
.<MacroContext>.contextSelectAll(filter)
→ Array<Object>
Deprecated:
This method has been deprecated and should no longer be used. See the <MacroContext>.contextFilter()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of <MacroContext>.contextFilter()
.<MacroContext>.createShadowWrapper(callback [, doneCallback [, startCallback]])
→ Function
Deprecated:
This method has been deprecated and should no longer be used. See the <MacroContext>.shadowHandler()
method for its replacement.
v2.14.0
: Introduced.v2.23.3
: Fixed an issue where shadows would fail for multiple layers of nested asynchronous code due to loss of context.v2.37.0
: Deprecated in favor of <MacroContext>.shadowHandler()
.Passage
API Instances of the Passage
object are returned by the Story.get()
static method.
All properties of Passage
objects should be treated as if they were read-only, as modifying them could result in unexpected behavior.
<Passage>.id
→ string
The passage's DOM-compatible ID, created from the slugified passage name.
v2.37.0
: Introduced.<Passage>.name
→ string
The name of the passage.
v2.37.0
: Introduced.<Passage>.tags
→ Array<string>
The tags of the passage.
v2.0.0
: Introduced.<Passage>.text
→ string
The raw text of the passage.
v2.0.0
: Introduced.<Passage>.processText()
→ string
Returns the processed text of the passage, created from applying nobr
tag and image passage processing to its raw text.
v2.0.0
: Introduced.var passage = Story.get("The Ducky");
passage.processText() → Returns the fully processed text of "The Ducky" passage
<Passage>.domId
→ string
Deprecated:
This property has been deprecated and should no longer be used. See the <Passage>.id
property for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of <Passage>.id
.<Passage>.title
→ string
Deprecated:
This property has been deprecated and should no longer be used. See the <Passage>.name
property for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of <Passage>.name
.<Passage>.description()
→ string
Deprecated: This method has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.Save
API Note: There are several configuration settings for saves that it would be wise for you to familiarize yourself with.
Warning: Browser saves—i.e., auto and slot saves—are largely incompatible with private browsing modes, which cause all in-browser storage mechanisms to either persist only for the lifetime of the browsing session or fail outright.
Note:
Adding additional properties directly to save data objects is not recommended. Instead, use the metadata
property.
A save data object has the following properties:
date
: (integer number
) The save's creation date (in milliseconds elapsed since epoch).desc
: (string
) The save's description.id
: (string
) The save ID. See the Config.saves.id
for details.metadata
: (optional, any
) The save's metadata, which must be JSON-serializable. This property exists only if specified. See the appropriate save API or Config.saves.metadata
for details.state
: (Object
) The marshaled story state. See below for details.type
: (Save.Type
) The save's type. See Save.Type
for details.version
: (optional, any
) The save's version. This property exists only if specified. See Config.saves.version
for details.The marshaled story state object, from the state
property, has the following properties:
expired
: (optional, Array<string>
) The array of expired moment passage names. This property exists only if moments have expired.history
: (Array<Object>
) The array of moment objects. See below for details.index
: (integer number
) The index of the active moment.seed
: (optional, string
) The seed of the seedable PRNG. This property exists only if the seedable PRNG is enabled.Each moment object, from the history
property's array, has the following properties:
pull
: (optional, integer number
) The current pull count of the seedable PRNG. This property exists only if the seedable PRNG is enabled.title
: (string
) The name of the associated passage.variables
: (Object
) The current variable store object, which contains sigil-less name ⇒ value pairs—e.g., $foo
exists as the property foo
.Save descriptor objects are only provided for browser saves and are identical to save data objects save that they do not include a state
property
Save.Type
Save types pseudo-enumeration. Used to denote the type of save.
v2.33.0
: Introduced.v2.37.0
: Changed into a public API.Type | Description |
---|---|
Save.Type.Auto |
Browser auto saves. |
Save.Type.Base64 |
Base64 string saves. |
Save.Type.Disk |
Disk saves. |
Save.Type.Slot |
Browser slot saves. |
Save.browser.size
→ integer number
The total number of existing browser saves, both auto and slot.
v2.37.0
: Introduced.The integer number
count of existing browser saves.
console.log(`There are currently ${Save.browser.size} browser saves`);
if (Save.browser.size > 0) {
/* Browser saves exist. */
}
if (Save.browser.size === 0) {
/* No browser saves exist. */
}
Save.browser.clear()
Deletes all existing browser saves, both auto and slot.
v2.37.0
: Introduced.Save.browser.clear();
Save.browser.continue()
→ Promise
Loads the most recent browser save, either auto or slot.
Note: The default UI includes a Continue button that makes use of this API. Thus, unless you disable or replace the default UI, players already have access to this functionality.
Warning: Saves cannot be loaded during startup and any attempt to do so will cause an error.
v2.37.0
: Introduced.A Promise
that simply resolves, or rejects with an error if the save could not be loaded.
Load the most recent browser save. This should be sufficient in the majority of cases.
if (Save.browser.size > 0) {
Save.browser.continue()
.then(() => {
/* Success. Show the passage. */
Engine.show();
})
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
}
else {
/* No browser saves exist. */
}
Save.browser.isEnabled()
→ boolean
Determines whether any browser saves are enabled, either auto, slot, or both.
v2.37.0
: Introduced.Boolean true
if any browser saves are enabled, elsewise false
.
if (Save.browser.isEnabled()) {
/* Some browser saves are enabled. */
}
Save.browser.newest()
→ Object
| null
Details the most recent browser save, either auto or slot.
v2.38.0
: Introduced.The descriptor object for the save if it exists, elsewise null
.
console.log('Descriptor of the most recent save:', Save.browser.newest());
Save.browser.auto.size
→ integer number
The total number of existing browser auto saves.
v2.37.0
: Introduced.The integer number
count of existing browser auto saves.
console.log(`There are currently ${Save.browser.auto.size} browser auto saves`);
if (Save.browser.auto.size > 0) {
/* Browser auto saves exist. */
}
if (Save.browser.auto.size === 0) {
/* No browser auto saves exist. */
}
Save.browser.auto.clear()
Deletes all existing auto saves.
v2.37.0
: Introduced.Save.browser.auto.clear();
Save.browser.auto.delete(index)
Deletes the auto save at the given index.
v2.37.0
: Introduced.index
: (integer number
) Auto save index (0
-based). Must be in the range 0
–Config.saves.maxAutoSaves
.An Error
instance.
Delete the auto save at the given index, handling failure.
try {
Save.browser.auto.delete(index);
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save.browser.auto.entries()
→ Array<Object>
Provides an array of details about all auto saves.
v2.37.0
: Introduced.An Array
of { index, info }
generic objects, or an empty Array
if no auto saves exist.
index
: (integer number
) The auto save's index (0
-based).info
: (Object
) The save's descriptor object.An Error
instance.
Save.browser.auto.entries().forEach(function (entry) {
console.log(`Descriptor of the auto save at index ${entry.index}:`, entry.info);
});
Save.browser.auto.get(index)
→ Object
Details the auto save at the given index.
v2.37.0
: Introduced.index
: (integer number
) Auto save index (0
-based). Must be in the range 0
–Config.saves.maxAutoSaves
.The descriptor object for the auto save if it exists, elsewise null
.
An Error
instance.
console.log(`Descriptor of the auto save at index ${index}:`, Save.browser.auto.get(index));
Save.browser.auto.has(index)
→ boolean
Determines whether the auto save at the given index exists.
v2.37.0
: Introduced.index
: (integer number
) Auto save index (0
-based). Must be in the range 0
–Config.saves.maxAutoSaves
.Boolean true
if the auto save exists, elsewise false
.
An Error
instance.
if (Save.browser.auto.has(index)) {
/* The auto save at the given index exists. */
}
Save.browser.auto.isEnabled()
→ boolean
Determines whether auto saves are enabled.
v2.37.0
: Introduced.Boolean true
if auto saves are enabled, elsewise false
.
if (Save.browser.auto.isEnabled()) {
/* Auto saves are enabled. */
}
Save.browser.auto.load(index)
→ Promise
Loads the auto save at the given index.
Warning: Saves cannot be loaded during startup and any attempt to do so will cause an error.
v2.37.0
: Introduced.index
: (integer number
) Auto save index (0
-based). Must be in the range 0
–Config.saves.maxAutoSaves
.A Promise
that simply resolves, or rejects with an error if the save could not be loaded.
Load auto save index 0
. This should be sufficient in the majority of cases.
Save.browser.auto.load(0)
.then(() => {
/* Success. Show the passage. */
Engine.show();
})
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
Save.browser.auto.save([desc [, metadata]])
Saves an auto save. If all auto save positions are full, replaces the oldest auto save.
v2.37.0
: Introduced.desc
: (optional, string
) The description of the auto save. If omitted or null
, defaults to the active passage's description.metadata
: (optional, any
) The data to be stored in the save object's metadata
property. Must be JSON-serializable.An Error
instance.
Save an auto save with the default description and no metadata, handling failure.
try {
Save.browser.auto.save();
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save an auto save with a description and no metadata, handling failure.
try {
Save.browser.auto.save("In the wilds");
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save an auto save with the default description and metadata, handling failure.
try {
const saveMetadata = {
chars : ['Celes', 'Locke', 'Edward'],
gold : 2345
};
Save.browser.auto.save(null, saveMetadata);
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save an auto save with a description and metadata, handling failure.
try {
const saveMetadata = {
chars : ['Celes', 'Locke', 'Edward'],
gold : 2345
};
Save.browser.auto.save("In the wilds", saveMetadata);
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save.browser.slot.size
→ integer number
The total number of existing browser slot saves.
v2.37.0
: Introduced.The integer number
count of existing browser slot saves.
console.log(`There are currently ${Save.browser.slot.size} browser slot saves`);
if (Save.browser.slot.size > 0) {
/* Browser slot saves exist. */
}
if (Save.browser.slot.size === 0) {
/* No browser slot saves exist. */
}
Save.browser.slot.clear()
Deletes all existing slot saves.
v2.37.0
: Introduced.Save.browser.slot.clear();
Save.browser.slot.delete(index)
Deletes the slot save at the given index.
v2.37.0
: Introduced.index
: (integer number
) Slot save index (0
-based). Must be in the range 0
–Config.saves.maxSlotSaves
.An Error
instance.
Delete the slot save at the given index, handling failure.
try {
Save.browser.slot.delete(index);
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save.browser.slot.entries()
→ Array<Object>
Provides an array of details about all slot saves.
v2.37.0
: Introduced.An Array
of { index, info }
generic objects, or an empty Array
if no slot saves exist.
index
: (integer number
) The slot save's index (0
-based).info
: (Object
) The save's descriptor object.An Error
instance.
Save.browser.slot.entries().forEach(function (entry) {
console.log(`Descriptor of the slot save at index ${entry.index}:`, entry.info);
});
Save.browser.slot.get(index)
→ Object
Details the slot save at the given index.
v2.37.0
: Introduced.index
: (integer number
) Slot save index (0
-based). Must be in the range 0
–Config.saves.maxSlotSaves
.The descriptor object for the slot save if it exists, elsewise null
.
An Error
instance.
console.log(`Descriptor of the slot save at index ${index}:`, Save.browser.slot.get(index));
Save.browser.slot.has(index)
→ boolean
Determines whether the slot save at the given index exists.
v2.37.0
: Introduced.index
: (integer number
) Slot save index (0
-based). Must be in the range 0
–Config.saves.maxSlotSaves
.Boolean true
if the slot save exists, elsewise false
.
An Error
instance.
if (Save.browser.slot.has(index)) {
/* The slot save at the given index exists. */
}
Save.browser.slot.isEnabled()
→ boolean
Determines whether slot saves are enabled.
v2.37.0
: Introduced.Boolean true
if slot saves are enabled, elsewise false
.
if (Save.browser.slot.isEnabled()) {
/* Slot saves are enabled. */
}
Save.browser.slot.load(index)
→ Promise
Loads the slot save at the given index.
Warning: Saves cannot be loaded during startup and any attempt to do so will cause an error.
v2.37.0
: Introduced.index
: (integer number
) Slot save index (0
-based). Must be in the range 0
–Config.saves.maxSlotSaves
.A Promise
that simply resolves, or rejects with an error if the save could not be loaded.
Load slot save index 0
. This should be sufficient in the majority of cases.
Save.browser.slot.load(0)
.then(() => {
/* Success. Show the passage. */
Engine.show();
})
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
Save.browser.slot.save(index, [desc [, metadata]])
Saves a slot save to the given index.
v2.37.0
: Introduced.index
: (integer number
) Slot save index (0
-based). Must be in the range 0
–Config.saves.maxSlotSaves
.desc
: (optional, string
) The description of the slot save. If omitted or null
, defaults to the active passage's description.metadata
: (optional, any
) The data to be stored in the save object's metadata
property. Must be JSON-serializable.An Error
instance.
Save to slot save index 0
with the default description and no metadata, handling failure.
try {
Save.browser.slot.save(0);
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save to slot save index 0
with a description and no metadata, handling failure.
try {
Save.browser.slot.save(0, "In the wilds");
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save to slot save index 0
with the default description and metadata, handling failure.
try {
const saveMetadata = {
chars : ['Celes', 'Locke', 'Edward'],
gold : 2345
};
Save.browser.slot.save(0, null, saveMetadata);
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save to slot save index 0
with a description and metadata, handling failure.
try {
const saveMetadata = {
chars : ['Celes', 'Locke', 'Edward'],
gold : 2345
};
Save.browser.slot.save(0, "In the wilds", saveMetadata);
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save.disk.export(filename)
Exports all existing browser saves as a bundle to disk, which may be restored via Save.disk.import()
.
v2.37.0
: Introduced.filename
: (string
) The base filename of the browser save export, which gets slugified to remove most symbols. Appended to this is a datestamp (format: YYYMMDD-hhmmss
) and the file extension .savesbundle
—e.g., "The Scooby Chronicles"
would result in the full filename: the-scooby-chronicles-{datestamp}.savesbundle
.An Error
instance.
Export all saves as a bundle with a base filename, handling failure.
try {
Save.disk.export('The 6th Fantasy');
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save.disk.import(event)
→ Promise
Imports a saves bundle from disk, created via Save.disk.export()
.
Note:
This method must be used as, or be called by, the change
event handler of an <input type="file">
element.
Warning: All existing browser saves will be deleted as part of restoring the exported save bundle.
v2.37.0
: Introduced.event
: (Event
) The event object that was passed to the change
event handler of the associated <input type="file">
element.A Promise
that simply resolves, or rejects with an error if the browser saves bundle could not be imported.
Import the saves bundle from disk, only handling failure. This should be sufficient in the majority of cases.
jQuery(document.createElement('input'))
.prop({
id : 'saves-import-file',
name : 'saves-import-file',
type : 'file'
})
.on('change', ev => {
// You must provide the event to Save.disk.import()
Save.disk.import(ev)
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
});
Import the saves bundle from disk, handling both success and failure.
jQuery(document.createElement('input'))
.prop({
id : 'saves-import-file',
name : 'saves-import-file',
type : 'file'
})
.on('change', function (ev) {
// You must provide the event to Save.browser.import()
Save.disk.import(ev)
.then(() => {
/* Success. Do something special. */
})
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
});
Save.disk.load(event)
→ Promise
Loads the given save from disk, created via Save.disk.save()
.
Note:
This method must be used as, or be called by, the change
event handler of an <input type="file">
element.
Warning: Saves cannot be loaded during startup and any attempt to do so will cause an error.
v2.37.0
: Introduced.event
: (Event
) The event object that was passed to the change
event handler of the associated <input type="file">
element.A Promise
that resolves with the save's metadata (any
), or rejects with an error if the save could not be loaded.
Load the disk save. This should be sufficient in the majority of cases.
jQuery(document.createElement('input'))
.prop({
id : 'saves-disk-load-file',
name : 'saves-disk-load-file',
type : 'file'
})
.on('change', ev => {
// You must provide the event to Save.disk.load()
Save.disk.load(ev)
.then(metadata => {
/* Success. Show the passage. */
Engine.show();
})
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
});
<<button "Load From Disk">>
<<script>>
jQuery(document.createElement('input'))
.prop('type', 'file')
.on('change', ev => {
// You must provide the event to Save.disk.load()
Save.disk.load(ev)
.then(metadata => {
/* Success. Show the passage. */
Engine.show();
})
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
})
.trigger('click');
<</script>>
<</button>>
Save.disk.save(filename [, metadata])
Saves the current story state to disk, which may be restored via Save.disk.load()
.
v2.37.0
: Introduced.filename
: (string
) The base filename of the disk save, which gets slugified to remove most symbols. Appended to this is a datestamp (format: YYYMMDD-hhmmss
) and the file extension .save
—e.g., "The Scooby Chronicles"
would result in the full filename: the-scooby-chronicles-{datestamp}.save
.metadata
: (optional, any
) The data to be stored in the save object's metadata
property. Must be JSON-serializable.An Error
instance.
Save with a base filename and no metadata, handling failure.
try {
Save.disk.save("The 6th Fantasy");
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save with a base filename and metadata, handling failure.
try {
const saveMetadata = {
chars : ['Celes', 'Locke', 'Edward'],
gold : 2345
};
Save.disk.save("The 6th Fantasy", saveMetadata);
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save.base64.export()
→ string
Exports all existing browser saves as a bundle to a Base64 string, which may be restored via Save.base64.import()
.
v2.37.0
: Introduced.An Error
instance.
Export all saves as a bundle, handling failure.
try {
const base64Save = Save.base64.export();
/* Do something with the saves bundle. */
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save.base64.import(bundle)
→ Promise
Imports the given Base64 saves bundle string, created via Save.base64.export()
.
Warning: All existing browser saves will be deleted as part of restoring the exported save bundle.
v2.37.0
: Introduced.bundle
: (string
) The Base64 saves bundle string.A Promise
that simply resolves, or rejects with an error if the browser saves bundle could not be imported.
Import the saves bundle, only handling failure. This should be sufficient in the majority of cases.
Save.base64.import(base64Bundle)
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
Import the saves bundle, handling both success and failure.
Save.base64.import(base64Bundle)
.then(() => {
/* Success. Do something special. */
})
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
Save.base64.load(save)
→ Promise
Loads the given Base64 save string, created via Save.base64.save()
.
Warning: Saves cannot be loaded during startup and any attempt to do so will cause an error.
v2.37.0
: Introduced.save
: (string
) The Base64 save string.A Promise
that resolves with the save's metadata (any
), or rejects with an error if the save could not be loaded.
Load the save string. This should be sufficient in the majority of cases.
Save.base64.load(base64Save)
.then(metadata => {
/* Success. Show the passage. */
Engine.show();
})
.catch(error => {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
});
Save.base64.save([metadata])
→ string
Saves the current story state as a Base64 string.
v2.37.0
: Introduced.metadata
: (optional, any
) The data to be stored in the save object's metadata
property. Must be JSON-serializable.A Base64 save string
.
An Error
instance.
Save without metadata, handling failure.
try {
const base64Save = Save.base64.save();
/* Do something with the save. */
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save with metadata, handling failure.
try {
const saveMetadata = {
chars : ['Celes', 'Locke', 'Edward'],
gold : 2345
};
const base64Save = Save.base64.save(saveMetadata);
/* Do something with the save. */
}
catch (error) {
/* Failure. Handle the error. */
console.error(error);
UI.alert(error);
}
Save.onLoad.size
→ integer number
The total number of currently registered on-load handlers.
v2.36.0
: Introduced.The integer number
count of currently registered on-load handlers.
console.log('There are %d onLoad handlers registered.', Save.onLoad.size);
Save.onLoad.add(handler)
Performs any required processing before the save data is loaded—e.g., upgrading out-of-date save data. The handler is passed one parameter, the save object to be processed. If it encounters an unrecoverable problem during its processing, it may throw an exception containing an error message; the message will be displayed to the player and loading of the save will be terminated.
v2.36.0
: Introduced.handler
: (Function
) The handler function to be executed upon the loading of a save.An Error
instance.
save
: (Object
) The save object to be processed.Save.onLoad.add(function (save) {
/* code to process the save object before it's loaded */
});
type
property and the Save.Type
pseudo-enumerationSave.onLoad.add(function (save) {
switch (save.type) {
case Save.Type.Auto: {
/* code to process an auto save object before it's loaded */
break;
}
case Save.Type.Base64: {
/* code to process a base64 save object before it's loaded */
break;
}
case Save.Type.Disk: {
/* code to process a disk save object before it's loaded */
break;
}
case Save.Type.Slot: {
/* code to process a slot save object before it's loaded */
break;
}
}
});
Save.onLoad.clear()
Deletes all currently registered on-load handlers.
v2.36.0
: Introduced.Save.onLoad.clear();
Save.onLoad.delete(handler)
→ boolean
Deletes the specified on-load handler.
v2.36.0
: Introduced.handler
: (Function
) The handler function to be deleted.Boolean true
if the handler existed, elsewise false
.
// Given:
// let myOnLoadHandler = function (save) {
// /* code to process the save object before it's loaded */
// };
// Save.onLoad.add(myOnLoadHandler);
Save.onLoad.delete(myOnLoadHandler);
Save.onSave.size
→ integer number
The total number of currently registered on-save handlers.
v2.36.0
: Introduced.The integer number
count of currently registered on-save handlers.
console.log('There are %d onSave handlers registered.', Save.onSave.size);
Save.onSave.add(handler)
Performs any required processing before the save data is saved. The handler is passed one parameter, the save object to be processed.
v2.36.0
: Introduced.v2.37.0
: Deprecated handlers' details
parameter.handler
: (Function
) The handler function to be executed upon the saving of a save.An Error
instance.
save
: (Object
) The save object to be processed.Save.onSave.add(function (save) {
/* code to process the save object before it's saved */
});
type
property and the Save.Type
pseudo-enumerationSave.onSave.add(function (save) {
switch (save.type) {
case Save.Type.Auto: {
/* code to process an auto save object before it's saved */
break;
}
case Save.Type.Base64: {
/* code to process a base64 save object before it's saved */
break;
}
case Save.Type.Disk: {
/* code to process a disk save object before it's saved */
break;
}
case Save.Type.Slot: {
/* code to process a slot save object before it's saved */
break;
}
}
});
Save.onSave.clear()
Deletes all currently registered on-save handlers.
v2.36.0
: Introduced.Save.onSave.clear();
Save.onSave.delete(handler)
→ boolean
Deletes the specified on-save handler.
v2.36.0
: Introduced.handler
: (Function
) The handler function to be deleted.Boolean true
if the handler existed, elsewise false
.
// Given:
// let myOnSaveHandler = function (save) {
// /* code to process the save object before it's saved */
// };
// Save.onSave.add(myOnSaveHandler);
Save.onSave.delete(myOnSaveHandler);
Save.clear()
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.clear()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.clear()
method.Save.get()
Deprecated: This method has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.Save.ok()
→ boolean
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.isEnabled()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.isEnabled()
method.Save.autosave.delete()
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.auto.delete()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.auto.delete()
method.Save.autosave.get()
→ Object
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.auto.get()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.auto.get()
method.Save.autosave.has()
→ boolean
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.auto.has()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.auto.has()
method.Save.autosave.load()
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.auto.load()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.auto.load()
method.Save.autosave.ok()
→ boolean
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.auto.isEnabled()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.auto.isEnabled()
method.Save.autosave.save([title [, metadata]])
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.auto.save()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.auto.save()
method.Save.slots.length
→ integer number
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.slot.size
property for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.slot.size
property.Save.slots.count()
→ integer number
Deprecated: This method has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.Save.slots.delete(slot)
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.slot.delete()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.slot.delete()
method.Save.slots.get(slot)
→ Object
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.slot.get()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.slot.get()
method.Save.slots.has(slot)
→ boolean
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.slot.has()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.slot.has()
method.Save.slots.isEmpty()
→ boolean
Deprecated: This method has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.Save.slots.load(slot)
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.slot.load()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.slot.load()
method.Save.slots.ok()
→ boolean
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.slot.isEnabled()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.slot.isEnabled()
method.Save.slots.save(slot [, title [, metadata]])
Deprecated:
This method has been deprecated and should no longer be used. See the Save.browser.slot.save()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.browser.slot.save()
method.Save.import(event)
Deprecated:
This method has been deprecated and should no longer be used. See the Save.disk.load()
method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.disk.load()
method.Save.export([filename [, metadata]])
Deprecated:
This method has been deprecated and should no longer be used. See the Save.disk.save()
method for its replacement.
v2.0.0
: Introduced.v2.8.0
: Added metadata
parameter.v2.37.0
: Deprecated in favor of the Save.disk.save()
method.Save.deserialize(saveStr)
→ any
| null
Deprecated:
This method has been deprecated and should no longer be used. See the Save.base64.load()
method for its replacement.
v2.21.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.base64.load()
method.Save.serialize([metadata])
→ string
| null
Deprecated:
This method has been deprecated and should no longer be used. See the Save.base64.save()
method for its replacement.
v2.21.0
: Introduced.v2.37.0
: Deprecated in favor of the Save.base64.save()
method.Setting
API Manages the Settings dialog and settings
object.
Warning:
Setting
API method calls must be placed within your project's JavaScript section (Twine 2: the Story JavaScript; Twine 1/Twee: a script
-tagged passage) or settings will not function correctly.
Setting.addHeader(name [, desc])
Adds a header to the Settings dialog.
v2.7.1
: Introduced.name
: (string
) Name of the header.desc
: (optional, string
) Description explaining the header in greater detail. May contain markup.// Setting up a basic header
Setting.addHeader("Content Settings");
// Setting up a header w/ a description
Setting.addHeader("Content Settings", "Settings controlling what content is made available in the game.");
Setting.addList(name, definition)
Adds the named property to the settings
object and a list control for it to the Settings dialog.
v2.0.0
: Introduced.v2.26.0
: Added desc
property to definition object.name
: (string
) Name of the settings
property to add, which the control will manage.definition
: (Object
) Definition of the setting and control.A list-type definition object should have some of the following properties:
label
: (string
) Label to use for the control.list
: (Array<string>
) The array of members.desc
: (optional, string
) Description explaining the control in greater detail. May contain markup.default
: (optional, [as list
array]) The default value for the setting and default state of the control. It should have the same value as one of the members of the list
array. Leaving it undefined means to use the first array member as the default.onInit
: (optional, Function
) The function to call during initialization. It is called with a result object as its sole parameter and, if possible, set as its this
.onChange
: (optional, Function
) The function to call when the control's state is changed. It is called with a result object as its sole parameter and, if possible, set as its this
.name
: (string
) Name of the settings
property.value
: ([as list
array]) The current value of the setting.default
: ([as list
array]) The default value for the setting.list
: (Array<string>
) The array of members.// Setting up a basic list control for the settings property 'difficulty'
Setting.addList("difficulty", {
label : "Choose a difficulty level.",
list : ["Easy", "Normal", "Hard", "INSANE"],
default : "Normal"
});
// Setting up a list control for the settings property 'theme' w/ callbacks
var settingThemeNames = ["(none)", "Bright Lights", "Charcoal", "Midnight", "Tinsel City"];
var settingThemeHandler = function () {
// cache the jQuery-wrapped <html> element
var $html = $("html");
// remove any existing theme class
$html.removeClass("theme-bright-lights theme-charcoal theme-midnight theme-tinsel-city");
// switch on the theme name to add the requested theme class
switch (settings.theme) {
case "Bright Lights":
$html.addClass("theme-bright-lights");
break;
case "Charcoal":
$html.addClass("theme-charcoal");
break;
case "Midnight":
$html.addClass("theme-midnight");
break;
case "Tinsel City":
$html.addClass("theme-tinsel-city");
break;
}
};
Setting.addList("theme", {
label : "Choose a theme.",
list : settingThemeNames,
onInit : settingThemeHandler,
onChange : settingThemeHandler
}); // default value not defined, so the first array member "(none)" is used
Setting.addRange(name, definition)
Adds the named property to the settings
object and a range control for it to the Settings dialog.
v2.26.0
: Introduced.name
: (string
) Name of the settings
property to add, which the control will manage.definition
: (Object
) Definition of the setting and control.A range-type definition object should have some of the following properties:
label
: (string
) Label to use for the control.max
: (number
) The maximum value.min
: (number
) The minimum value.step
: (number
) Limits the increments to which the value may be set. It must be evenly divisible into the full range—i.e., max - min
.desc
: (optional, string
) Description explaining the control in greater detail. May contain markup.default
: (optional, number
) The default value for the setting and default state of the control. Leaving it undefined means to use the value of max
as the default.onInit
: (optional, Function
) The function to call during initialization. It is called with a result object as its sole parameter and, if possible, set as its this
.onChange
: (optional, Function
) The function to call when the control's state is changed. It is called with a result object as its sole parameter and, if possible, set as its this
.name
: (string
) Name of the settings
property.value
: (number
) The current value of the setting.default
: (number
) The default value for the setting.max
: (number
) The maximum value for the setting.min
: (number
) The minimum value for the setting.step
: (number
) The step value for the setting.// Setting up a volume control for the settings property 'masterVolume' w/ callback
var settingMasterVolumeHandler = function () {
SimpleAudio.volume(settings.masterVolume / 10);
};
Setting.addRange("masterVolume", {
label : "Master volume.",
min : 0,
max : 10,
step : 1,
onInit : settingMasterVolumeHandler,
onChange : settingMasterVolumeHandler
}); // default value not defined, so max value (10) is used
Setting.addToggle(name, definition)
Adds the named property to the settings
object and a toggle control for it to the Settings dialog.
v2.0.0
: Introduced.v2.26.0
: Added desc
property to definition object.name
: (string
) Name of the settings
property to add, which the control will manage.definition
: (Object
) Definition of the setting and control.A toggle-type definition object should have some of the following properties:
label
: (string
) Label to use for the control.desc
: (optional, string
) Description explaining the control in greater detail. May contain markup.default
: (optional, boolean
) The default value for the setting and default state of the control. Leaving it undefined means to use false
as the default.onInit
: (optional, Function
) The function to call during initialization. It is called with a result object as its sole parameter and, if possible, set as its this
.onChange
: (optional, Function
) The function to call when the control's state is changed. It is called with a result object as its sole parameter and, if possible, set as its this
.name
: (string
) Name of the settings
property.value
: (boolean
) The current value of the setting.default
: (boolean
) The default value for the setting.// Setting up a basic toggle control for the settings property 'mature'
Setting.addToggle("mature", {
label : "Content for mature audiences?"
}); // default value not defined, so false is used
// Setting up a toggle control for the settings property 'widescreen' w/ callbacks
var settingWidescreenHandler = function () {
if (settings.widescreen) { // is true
$("html").addClass("widescreen");
}
else { // is false
$("html").removeClass("widescreen");
}
};
Setting.addToggle("widescreen", {
label : "Allow the story to use the full width of your browser window?",
default : false,
onInit : settingWidescreenHandler,
onChange : settingWidescreenHandler
});
And the requisite CSS style rule:
html.widescreen #passages {
max-width: none;
}
Setting.addValue(name [, definition])
Adds the named property to the settings
object.
Note: Does not add a control to the Settings dialog.
v2.37.0
: Introduced.name
: (string
) Name of the settings
property to add.definition
: (optional, Object
) Definition of the setting. May be omitted.A value-type definition object should have some of the following properties:
default
: (optional, any
) The default value for the setting.onInit
: (optional, Function
) The function to call during initialization. It is called with a result object as its sole parameter and, if possible, set as its this
.onChange
: (optional, Function
) The function to call when the control's state is changed. It is called with a result object as its sole parameter and, if possible, set as its this
.name
: (string
) Name of the settings
property.value
: (any
) The current value of the setting.default
: (any
) The default value for the setting.Setting.addValue("someSetting");
Setting.addValue("anotherSetting", {
default : 42,
onInit : function () {
/* Do something when the setting is initialized. */
},
onChange : function () {
/* Do something when the setting is changed. */
}
});
Setting.getValue(name)
→ any
Returns the setting's current value.
Note:
Calling this method is equivalent to using settings[name]
.
v2.37.0
: Introduced.// Assume `disableAudio` is a toggle-type setting.
if (Setting.getValue("disableAudio")) {
/* Audio should be disabled. */
}
Setting.load()
Loads the settings from storage.
Note: This method is automatically called during startup, so you should never need to call it manually.
v2.0.0
: Introduced.Setting.load();
Setting.reset([name])
Resets the setting with the given name to its default value. If no name is given, resets all settings.
v2.0.0
: Introduced.name
: (optional, string
) Name of the settings
object property to reset.// Reset the setting 'difficulty'
Setting.reset("difficulty");
// Reset all settings
Setting.reset();
Setting.save()
Saves the settings to storage.
Note:
The controls of the Settings dialog and the Setting.setValue()
method automatically call this method when settings are changed, so you should normally never need to call this method manually. Only when directly modifying the values of settings
object properties, outside of the controls or Setting.setValue()
method, would you need to call this method.
v2.0.0
: Introduced.Setting.save();
Setting.setValue(name, value)
Sets the setting's value.
Note:
This method automatically calls the Setting.save()
method.
Warning: If manually changing a setting that has an associated control, be mindful that the value you set makes sense for the setting in question, elsewise shenanigans could occur—e.g., don't set a range-type setting to non-number or out-of-range values.
v2.37.0
: Introduced.name
: (string
) Name of the settings
property.value
: (any
) The new value for the setting.Setting.setValue("theme", "dark");
settings
object A prototype-less generic object whose properties and values are defined by the Setting.addList()
, Setting.addRange()
, Setting.addToggle()
, and Setting.addValue()
methods.
For all types of setting types except value-types, the values of its properties are automatically managed by the Settings dialog controls. If necessary, you may manually change setting values via the Setting.setValue()
method.
Note:
You may also manually change setting values by assigning directly to the associated property—e.g., settings["mode"] = "day"
. Doing so, however, does not automatically save any values so updated, thus you must manually call the Setting.save()
method afterwards.
Warning: If manually changing a setting that has an associated control, be mindful that the value you set makes sense for the setting in question, elsewise shenanigans could occur—e.g., don't set a range-type setting to non-number or out-of-range values.
v2.0.0
: Introduced.SimpleAudio
API The core audio subsystem and backend for the audio macros.
See Also:
AudioTrack
API, AudioRunner
API, and AudioList
API.
The audio subsystem is based upon the HTML Media Elements APIs and comes with some built-in limitations:
<<timed>>
—though this ultimately depends on various factors. A simple solution for the former is to use some kind of click/touch-through screen—e.g., a splash screen, which the player goes through to the real starting passage. The latter is harder to resolve, so is best avoided.SimpleAudio.load()
Pauses playback of all currently registered tracks and, if they're not already in the process of loading, force them to drop any existing data and begin loading.
Warning: This should not be done lightly if your audio sources are on the network, as it forces players to begin downloading them.
v2.28.0
: Introduced.SimpleAudio.load();
SimpleAudio.loadWithScreen()
Displays the loading screen until all currently registered audio tracks have either loaded to a playable state or aborted loading due to errors. The loading process is as described in SimpleAudio.load()
.
Warning: This should not be done lightly if your audio sources are on the network, as it forces players to begin downloading them.
v2.28.0
: Introduced.SimpleAudio.loadWithScreen();
SimpleAudio.mute([state])
→ get: boolean
| set: undefined
Gets or sets the mute state for the master volume (default: false
).
v2.28.0
: Introduced.state
: (optional, boolean
) The mute state.// Get the current master volume mute state.
var isMuted = SimpleAudio.mute();
// Mute the master volume.
SimpleAudio.mute(true);
// Unmute the master volume.
SimpleAudio.mute(false);
SimpleAudio.muteOnHidden([state])
→ get: boolean
| set: undefined
Gets or sets the mute-on-hidden state for the master volume (default: false
). The mute-on-hidden state controls whether the master volume is automatically muted/unmuted when the story's browser tab loses/gains visibility. Loss of visibility is defined as when the browser window is either switched to another tab or minimized.
v2.28.0
: Introduced.state
: (optional, boolean
) The mute-on-hidden state.// Get the current master volume mute-on-hidden state.
var isMuteOnHidden = SimpleAudio.muteOnHidden();
// Enable automatic muting of the master volume when visibility is lost.
SimpleAudio.muteOnHidden(true);
// Disable automatic muting of the master volume when visibility is lost.
SimpleAudio.muteOnHidden(false);
SimpleAudio.select(selector)
→ AudioRunner
object Returns an AudioRunner
instance for the tracks matching the given selector.
v2.28.0
: Introduced.v2.37.0
: Added :stopped
predefined group ID.selector
: (string
) The list of audio track(s) and/or group ID(s), separated by spaces. There are several predefined group IDs (:all
, :looped
, :muted
, :paused
, :playing
, :stopped
). The :not()
group modifier syntax (groupId:not(selector)
) allows a group to have some of its tracks excluded from selection.SimpleAudio.select(":ui") → Returns the AudioRunner instance for the tracks matching ":ui"
// Return the AudioTrack instance matching "swamped" and do something with it
SimpleAudio.select("swamped").volume(1).play();
// Start playback of paused audio tracks
SimpleAudio.select(":paused").play();
// Pause playback of playing audio tracks
SimpleAudio.select(":playing").pause();
// Stop playback of playing audio tracks
SimpleAudio.select(":playing").stop();
// Stop playback of all audio tracks (not uniquely part of a playlist)
SimpleAudio.select(":all").stop();
// Stop playback of playing audio tracks except those in the ":ui" group
SimpleAudio.select(":playing:not(:ui)").stop();
// Change the volume of all audio tracks except those in the ":ui" group
// to 40%, without changing the current playback state
SimpleAudio.select(":all:not(:ui)").volume(0.40);
SimpleAudio.stop()
Stops playback of all currently registered tracks.
v2.28.0
: Introduced.SimpleAudio.stop();
SimpleAudio.unload()
Stops playback of all currently registered tracks and force them to drop any existing data.
Note: Once a track has been unloaded, playback cannot occur until it is reloaded.
v2.28.0
: Introduced.SimpleAudio.unload();
SimpleAudio.volume([level])
→ get: number
| set: undefined
Gets or sets the master volume level (default: 1
).
v2.28.0
: Introduced.level
: (optional, number
) The volume level to set. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.// Get the current master volume level.
var currentMasterVolume = SimpleAudio.volume();
// Set the master volume level to 75%.
SimpleAudio.volume(0.75);
SimpleAudio.tracks.add(trackId, sources…)
Adds an audio track with the given track ID.
v2.28.0
: Introduced.trackId
: (string
) The ID of the track, which will be used to reference it.sources
: (string
… | Array<string>
) The audio sources for the track, which may be a list of sources or an array. Only one is required, though supplying additional sources in differing formats is recommended, as no single format is supported by all browsers. A source must be either a URL (absolute or relative) to an audio resource, the name of an audio passage, or a data URI. In rare cases where the audio format cannot be automatically detected from the source (URLs are parsed for a file extension, data URIs are parsed for the media type), a format specifier may be prepended to the front of each source to manually specify the format (syntax: formatId|
, where formatId
is the audio format—generally, whatever the file extension would normally be; e.g., mp3
, mp4
, ogg
, weba
, wav
).// Cache a track with the ID "boom" and one source via relative URL
SimpleAudio.tracks.add("boom", "media/audio/explosion.mp3");
// Cache a track with the ID "boom" and one source via audio passage
SimpleAudio.tracks.add("boom", "explosion");
// Cache a track with the ID "bgm_space" and two sources via relative URLs
SimpleAudio.tracks.add("bgm_space", "media/audio/space_quest.mp3", "media/audio/space_quest.ogg");
// Cache a track with the ID "what" and one source via URL with a format specifier
SimpleAudio.tracks.add("what", "mp3|http://an-audio-service.com/a-user/a-track-id");
SimpleAudio.tracks.clear()
Deletes all audio tracks.
Note: Cannot delete tracks solely under the control of a playlist.
v2.28.0
: Introduced.SimpleAudio.tracks.clear();
SimpleAudio.tracks.delete(trackId)
Deletes the audio track with the given track ID.
Note: Cannot delete tracks solely under the control of a playlist.
Warning: Does not currently remove the track from either groups or playlists. Thus, any groups or playlists containing the deleted track should be rebuilt.
v2.28.0
: Introduced.trackId
: (string
) The ID of the track.SimpleAudio.tracks.delete("bgm_space");
SimpleAudio.tracks.get(trackId)
→ AudioTrack
| null
Returns the AudioTrack
instance with the given track ID, or null
on failure.
Note:
To affect multiple tracks and/or groups at once, see the SimpleAudio.select()
method.
v2.28.0
: Introduced.trackId
: (string
) The ID of the track.SimpleAudio.tracks.get("swamped") → Returns the AudioTrack instance matching "swamped"
// Return the AudioTrack instance matching "swamped" and do something with it
SimpleAudio.tracks.get("swamped").volume(1).play();
SimpleAudio.tracks.has(trackId)
→ boolean
Returns whether an audio track with the given track ID exists.
v2.28.0
: Introduced.trackId
: (string
) The ID of the track.if (SimpleAudio.tracks.has("bgm_space")) {
// Track "bgm_space" exists.
}
SimpleAudio.groups.add(groupId, trackIds…)
Adds an audio group with the given group ID. Groups are useful for applying actions to multiple tracks simultaneously and/or excluding the included tracks from a larger set when applying actions.
Note: If you want to play tracks in a sequence, then you want a playlist instead.
v2.28.0
: Introduced.v2.37.0
: Added :stopped
predefined group ID.groupId
: (string
) The ID of the group, which will be used to reference it and must begin with a colon. NOTE: There are several predefined group IDs (:all
, :looped
, :muted
, :paused
, :playing
, :stopped
) and the :not
group modifier that cannot be reused/overwritten.trackIds
: (string
… | Array<string>
) The IDs of the tracks to make part of the group, which may be a list of track IDs or an array.// Set up a group ":ui" with the tracks: "ui_beep", "ui_boop", and "ui_swish"
SimpleAudio.groups.add(":ui", "ui_beep", "ui_boop", "ui_swish");
SimpleAudio.groups.clear()
Deletes all audio groups.
Note: Only deletes the groups themselves, does not affect their component tracks.
v2.28.0
: Introduced.SimpleAudio.groups.clear();
SimpleAudio.groups.delete(groupId)
Deletes the audio group with the given group ID.
Note: Only deletes the group itself, does not affect its component tracks.
v2.28.0
: Introduced.groupId
: (string
) The ID of the group.SimpleAudio.groups.delete(":ui");
SimpleAudio.groups.get(groupId)
→ Array<string>
| null
Returns the array of track IDs with the given group ID, or null
on failure.
Note:
To actually affect multiple tracks and/or groups, see the SimpleAudio.select()
method.
v2.28.0
: Introduced.groupId
: (string
) The ID of the group.SimpleAudio.groups.get(":ui") → Returns the array of track IDs matching ":ui"
SimpleAudio.groups.has(groupId)
→ boolean
Returns whether an audio group with the given group ID exists.
v2.28.0
: Introduced.groupId
: (string
) The ID of the group.if (SimpleAudio.groups.has(":ui")) {
// Group ":ui" exists.
}
SimpleAudio.lists.add(listId, sources…)
Adds a playlist with the given list ID. Playlists are useful for playing tracks in a sequence—i.e., one after another.
Note: If you simply want to apply actions to multiple tracks simultaneously, then you want a group instead.
v2.28.0
: Introduced.v2.29.0
: Changed descriptor object copy
property to own
.listId
: (string
) The ID of the list, which will be used to reference it.sources
: (string
| Object
| Array<string | Object>
) The track IDs or descriptors of the tracks to make part of the list, which may be specified as a list or an array.Track descriptor objects come in two forms and should have some of the noted properties:
{ id, [own], [volume] }
id
: (string
) The ID of an existing track.own
: (optional, boolean
) When true
, signifies that the playlist should create its own independent copy of the track, rather than simply referencing the existing instance. Owned copies are solely under the control of their playlist and cannot be selected with either the SimpleAudio.tracks.get()
method or the SimpleAudio.select()
method.volume
: (optional, number
) The base volume level of the track within the playlist. If omitted, defaults to the track's current volume. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.{ sources, [volume] }
sources
: (Array<string>
) The audio sources for the track. Only one is required, though supplying additional sources in differing formats is recommended, as no single format is supported by all browsers. A source must be either a URL (absolute or relative) to an audio resource, the name of an audio passage, or a data URI. In rare cases where the audio format cannot be automatically detected from the source (URLs are parsed for a file extension, data URIs are parsed for the media type), a format specifier may be prepended to the front of each source to manually specify the format (syntax: formatId|
, where formatId
is the audio format—generally, whatever the file extension would normally be; e.g., mp3
, mp4
, ogg
, weba
, wav
).volume
: (optional, number
) The base volume level of the track within the playlist. If omitted, defaults to 1
(loudest). Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.// Add existing tracks at their current volumes
SimpleAudio.lists.add("bgm_lacuna", "swamped", "heavens_a_lie", "closer", "to_the_edge");
SimpleAudio.lists.add("bgm_lacuna",
// Add existing track "swamped" at its current volume
"swamped",
// Add existing track "heavens_a_lie" at 50% volume
{
id : "heavens_a_lie",
volume : 0.5
},
// Add an owned copy of existing track "closer" at its current volume
{
id : "closer",
own : true
},
// Add an owned copy of existing track "to_the_edge" at 100% volume
{
id : "to_the_edge",
own : true,
volume : 1
}
);
SimpleAudio.lists.add("bgm_lacuna",
// Add a track from the given sources at the default volume (100%)
{
sources : ["media/audio/Swamped.mp3"]
}
// Add a track from the given sources at 50% volume
{
sources : ["media/audio/Heaven's_A_Lie.mp3"],
volume : 0.5
},
// Add a track from the given sources at the default volume (100%)
{
sources : ["media/audio/Closer.mp3"]
},
// Add a track from the given sources at 100% volume
{
sources : ["media/audio/To_The_Edge.mp3"],
volume : 1
}
);
SimpleAudio.lists.clear()
Deletes all playlists.
v2.28.0
: Introduced.SimpleAudio.lists.clear();
SimpleAudio.lists.delete(listId)
Deletes the playlist with the given list ID.
v2.28.0
: Introduced.listId
: (string
) The ID of the playlist.SimpleAudio.lists.delete("bgm_lacuna");
SimpleAudio.lists.get(listId)
→ AudioList
| null
Returns the AudioList
instance with the given list ID, or null
on failure.
v2.28.0
: Introduced.listId
: (string
) The ID of the playlist.SimpleAudio.lists.get("bgm_lacuna") → Returns the AudioList instance matching "bgm_lacuna"
// Return the AudioList instance matching "bgm_lacuna" and do something with it
SimpleAudio.lists.get("bgm_lacuna").volume(1).loop(true).play();
SimpleAudio.lists.has(listId)
→ boolean
Returns whether a playlist with the given list ID exists.
v2.28.0
: Introduced.listId
: (string
) The ID of the playlist.if (SimpleAudio.lists.has("bgm_lacuna")) {
// Playlist "bgm_lacuna" exists.
}
AudioTrack
API Audio tracks encapsulate and provide a consistent interface to an audio resource.
See Also:
SimpleAudio
API, AudioRunner
API, and AudioList
API.
<AudioTrack>.clone()
→ AudioTrack
Returns a new independent copy of the track.
v2.28.0
: Introduced.var trackCopy = aTrack.clone();
<AudioTrack>.duration()
→ number
Returns the track's total playtime in seconds, Infinity
for a stream, or NaN
if no metadata exists.
v2.28.0
: Introduced.var trackLength = aTrack.duration();
<AudioTrack>.fade(duration , toVol [, fromVol])
→ Promise
Starts playback of the track and fades it between the specified starting and destination volume levels over the specified number of seconds.
Note:
The Config.audio.pauseOnFadeToZero
setting (default: true
) determines whether the audio subsystem automatically pauses tracks that have been faded to 0
volume (silent).
v2.28.0
: Introduced.duration
: (number
) The number of seconds over which the track should be faded.toVol
: (number
) The destination volume level.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the track's current volume level.// Fade the track from volume 0 to 1 over 6 seconds.
aTrack.fade(6, 1, 0);
<AudioTrack>.fadeIn(duration [, fromVol])
→ Promise
Starts playback of the track and fades it from the specified volume level to 1
(loudest) over the specified number of seconds.
v2.28.0
: Introduced.v2.29.0
: Updated to return a Promise
.duration
: (number
) The number of seconds over which the track should be faded.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the track's current volume level.// Fade the track in from volume 0 over 5 seconds.
aTrack.fadeIn(5, 0);
<AudioTrack>.fadeOut(duration [, fromVol])
→ Promise
Starts playback of the track and fades it from the specified volume level to 0
(silent) over the specified number of seconds.
Note:
The Config.audio.pauseOnFadeToZero
setting (default: true
) determines whether the audio subsystem automatically pauses tracks that have been faded to 0
volume (silent).
v2.28.0
: Introduced.v2.29.0
: Updated to return a Promise
.duration
: (number
) The number of seconds over which the track should be faded.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the track's current volume level.// Fade the track out from volume 1 over 8 seconds.
aTrack.fadeOut(8, 1);
<AudioTrack>.fadeStop()
Interrupts an in-progress fade of the track, or does nothing if no fade is progressing.
Note: This does not alter the volume level.
v2.28.0
: Introduced.aTrack.fadeStop();
<AudioTrack>.hasData()
→ boolean
Returns whether enough data has been loaded to play the track through to the end without interruption.
Note: This is an estimate calculated by the browser based upon the currently downloaded data and the download rate.
v2.28.0
: Introduced.if (aTrack.hasData()) {
/* do something */
}
<AudioTrack>.hasMetadata()
→ boolean
Returns whether, at least, the track's metadata has been loaded.
v2.28.0
: Introduced.if (aTrack.hasMetadata()) {
/* do something */
}
<AudioTrack>.hasNoData()
→ boolean
Returns whether none of the track's data has been loaded.
v2.28.0
: Introduced.if (aTrack.hasNoData()) {
/* do something */
}
<AudioTrack>.hasSomeData()
→ boolean
Returns whether, at least, some of the track's data has been loaded.
Tip:
The <AudioTrack>.hasData()
method is generally more useful.
v2.28.0
: Introduced.if (aTrack.hasSomeData()) {
/* do something */
}
<AudioTrack>.hasSource()
→ boolean
Returns whether any valid sources were registered.
v2.28.0
: Introduced.if (aTrack.hasSource()) {
/* do something */
}
<AudioTrack>.isEnded()
→ boolean
Returns whether playback of the track has ended.
v2.28.0
: Introduced.if (aTrack.isEnded()) {
/* do something */
}
<AudioTrack>.isFading()
→ boolean
Returns whether a fade is in-progress on the track.
v2.28.0
: Introduced.if (aTrack.isFading()) {
/* do something */
}
<AudioTrack>.isFailed()
→ boolean
Returns whether an error has occurred.
v2.28.0
: Introduced.if (aTrack.isFailed()) {
/* do something */
}
<AudioTrack>.isLoading()
→ boolean
Returns whether the track is loading data.
v2.28.0
: Introduced.if (aTrack.isLoading()) {
/* do something */
}
<AudioTrack>.isPaused()
→ boolean
Returns whether playback of the track has been paused.
v2.28.0
: Introduced.if (aTrack.isPaused()) {
/* do something */
}
<AudioTrack>.isPlaying()
→ boolean
Returns whether the track is playing.
v2.28.0
: Introduced.if (aTrack.isPlaying()) {
/* do something */
}
<AudioTrack>.isSeeking()
→ boolean
Returns whether the track is seeking.
v2.28.0
: Introduced.if (aTrack.isSeeking()) {
/* do something */
}
<AudioTrack>.isStopped()
→ boolean
Returns whether playback of the track has been stopped.
v2.29.0
: Introduced.if (aTrack.isStopped()) {
/* do something */
}
<AudioTrack>.isUnavailable()
→ boolean
Returns whether the track is currently unavailable for playback. Possible reasons include: no valid sources are registered, no sources are currently loaded, an error has occurred.
v2.28.0
: Introduced.if (aTrack.isUnavailable()) {
/* do something */
}
<AudioTrack>.isUnloaded()
→ boolean
Returns whether the track's sources are currently unloaded.
v2.28.0
: Introduced.if (aTrack.isUnloaded()) {
/* do something */
}
<AudioTrack>.load()
Pauses playback of the track and, if it's not already in the process of loading, forces it to drop any existing data and begin loading.
Warning: This should not be done lightly if your audio sources are on the network, as it forces players to begin downloading them.
v2.28.0
: Introduced.aTrack.load();
<AudioTrack>.loop([state])
→ get: boolean
| set: AudioTrack
Gets or sets the track's repeating playback state (default: false
). When used to set the loop state, returns a reference to the current AudioTrack
instance for chaining.
v2.28.0
: Introduced.state
: (optional, boolean
) The loop state.// Get the track's current loop state.
var isLooped = aTrack.loop();
// Loop the track.
aTrack.loop(true);
// Unloop the track.
aTrack.loop(false);
<AudioTrack>.mute([state])
→ get: boolean
| set: AudioTrack
Gets or sets the track's volume mute state (default: false
). When used to set the mute state, returns a reference to the current AudioTrack
instance for chaining.
v2.28.0
: Introduced.state
: (optional, boolean
) The mute state.// Get the track's current volume mute state.
var isMuted = aTrack.mute();
// Mute the track's volume.
aTrack.mute(true);
// Unmute the track's volume.
aTrack.mute(false);
<AudioTrack>.off(...args)
→ AudioTrack
Removes event handlers from the track. Returns a reference to the current AudioTrack
instance for chaining.
Note:
Shorthand for jQuery's .off()
method applied to the audio element.
Warning:
The SimpleAudio
APIs use events internally for various pieces of functionality. To prevent conflicts, it is strongly suggested that you specify a custom user namespace—e.g., .myEvents
—when attaching your own handlers. It is further strongly suggested that you provide that same custom user namespace when removing them.
v2.28.0
: Introduced.See:
<jQuery>.off()
in the jQuery API docs for more information.
// Remove any handlers for the ended event.
aTrack.off('ended.myEvents');
<AudioTrack>.on(...args)
→ AudioTrack
Attaches event handlers to the track. Returns a reference to the current AudioTrack
instance for chaining.
Note:
Shorthand for jQuery's .on()
method applied to the audio element.
Warning:
The SimpleAudio
APIs use events internally for various pieces of functionality. To prevent conflicts, it is strongly suggested that you specify a custom user namespace—e.g., .myEvents
—when attaching your own handlers. It is further strongly suggested that you provide that same custom user namespace when removing them.
v2.28.0
: Introduced.See:
<jQuery>.on()
in the jQuery API docs for more information.
// Add a handler for the ended event.
aTrack.on('ended.myEvents', function () {
/* do something */
});
<AudioTrack>.one(...args)
→ AudioTrack
Attaches single-use event handlers to the track. Returns a reference to the current AudioTrack
instance for chaining.
Note:
Shorthand for jQuery's .one()
method applied to the audio element.
Warning:
The SimpleAudio
APIs use events internally for various pieces of functionality. To prevent conflicts, it is strongly suggested that you specify a custom user namespace—e.g., .myEvents
—when attaching your own handlers. It is further strongly suggested that you provide that same custom user namespace when removing them.
v2.28.0
: Introduced.See:
<jQuery>.one()
in the jQuery API docs for more information.
// Add a single-use handler for the ended event.
aTrack.one('ended.myEvents', function () {
/* do something */
});
<AudioTrack>.pause()
Pauses playback of the track.
v2.28.0
: Introduced.aTrack.pause();
<AudioTrack>.play()
→ Promise
Begins playback of the track.
v2.28.0
: Introduced.aTrack.play();
Promise
aTrack.play()
.then(function () {
console.log('The track is playing.');
})
.catch(function (problem) {
console.warn('There was a problem with playback: ' + problem);
});
<AudioTrack>.playWhenAllowed()
Begins playback of the track or, failing that, sets the track to begin playback as soon as the player has interacted with the document.
v2.28.0
: Introduced.aTrack.playWhenAllowed();
<AudioTrack>.remaining()
→ number
Returns how much remains of the track's total playtime in seconds, Infinity
for a stream, or NaN
if no metadata exists.
v2.28.0
: Introduced.var trackRemains = aTrack.remaining();
<AudioTrack>.stop()
Stops playback of the track.
v2.28.0
: Introduced.someTrack.stop();
<AudioTrack>.time([seconds])
→ get: number
| set: AudioTrack
Gets or sets the track's current time in seconds. When used to set a value, returns a reference to the current AudioTrack
instance for chaining.
v2.28.0
: Introduced.seconds
: (optional, number
) The time to set. Valid values are floating-point numbers in the range 0
(start) to the maximum duration—e.g., 60
is 60
is sixty seconds in, 90.5
is ninety-point-five seconds in.// Get the track's current time.
var trackTime = aTrack.time();
// Set the track's current time to 30 seconds from its beginning.
aTrack.time(30);
// Set the track's current time to 30 seconds from its end.
aTrack.time(aTrack.duration() - 30);
<AudioTrack>.unload()
Stops playback of the track and forces it to drop any existing data.
Note: Once unloaded, playback cannot occur until the track's data is loaded again.
v2.28.0
: Introduced.aTrack.unload();
<AudioTrack>.volume([level])
→ get: number
| set: AudioTrack
Gets or sets the track's volume level (default: 1
). When used to set the volume, returns a reference to the current AudioTrack
instance for chaining.
v2.28.0
: Introduced.level
: (optional, number
) The volume level to set. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.// Get the track's current volume level.
var trackVolume = aTrack.volume();
// Set the track's volume level to 75%.
aTrack.volume(0.75);
AudioRunner
API Audio runners are useful for performing actions on multiple tracks at once.
See Also:
SimpleAudio
API, AudioTrack
API, and AudioList
API.
<AudioRunner>.fade(duration , toVol [, fromVol])
Starts playback of the selected tracks and fades them between the specified starting and destination volume levels over the specified number of seconds.
Note:
The Config.audio.pauseOnFadeToZero
setting (default: true
) determines whether the audio subsystem automatically pauses tracks that have been faded to 0
volume (silent).
v2.28.0
: Introduced.duration
: (number
) The number of seconds over which the tracks should be faded.toVol
: (number
) The destination volume level.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the tracks' current volume level.// Fade the selected tracks from volume 0 to 1 over 6 seconds.
someTracks.fade(6, 1, 0);
<AudioRunner>.fadeIn(duration [, fromVol])
Starts playback of the selected tracks and fades them from the specified volume level to 1
(loudest) over the specified number of seconds.
v2.28.0
: Introduced.duration
: (number
) The number of seconds over which the tracks should be faded.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the tracks' current volume level.// Fade the selected tracks in from volume 0 over 5 seconds.
someTracks.fadeIn(5, 0);
<AudioRunner>.fadeOut(duration [, fromVol])
Starts playback of the selected tracks and fades them from the specified volume level to 0
(silent) over the specified number of seconds.
Note:
The Config.audio.pauseOnFadeToZero
setting (default: true
) determines whether the audio subsystem automatically pauses tracks that have been faded to 0
volume (silent).
v2.28.0
: Introduced.duration
: (number
) The number of seconds over which the tracks should be faded.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the tracks' current volume level.// Fade the selected tracks out from volume 1 over 8 seconds.
someTracks.fadeOut(8, 1);
<AudioRunner>.fadeStop()
Interrupts an in-progress fade of the selected tracks, or does nothing if no fade is progressing.
Note: This does not alter the volume level.
v2.28.0
: Introduced.someTracks.fadeStop();
<AudioRunner>.load()
Pauses playback of the selected tracks and, if they're not already in the process of loading, forces them to drop any existing data and begin loading.
Warning: This should not be done lightly if your audio sources are on the network, as it forces players to begin downloading them.
v2.28.0
: Introduced.someTracks.load();
<AudioRunner>.loop(state)
→ AudioRunner
object Sets the selected tracks' repeating playback state (default: false
). Returns a reference to the current AudioRunner
instance for chaining.
v2.28.0
: Introduced.state
: (boolean
) The loop state.// Loop the selected tracks.
someTracks.loop(true);
// Unloop the selected tracks.
someTracks.loop(false);
<AudioRunner>.mute(state)
→ AudioRunner
object Sets the selected tracks' volume mute state (default: false
). Returns a reference to the current AudioRunner
instance for chaining.
v2.28.0
: Introduced.state
: (boolean
) The mute state.// Mute the selected tracks' volume.
someTracks.mute(true);
// Unmute the selected tracks' volume.
someTracks.mute(false);
<AudioRunner>.off(...args)
→ AudioRunner
object Removes event handlers from the selected tracks. Returns a reference to the current AudioRunner
instance for chaining.
Note:
Shorthand for jQuery's .off()
method applied to each of the audio elements.
Warning:
The SimpleAudio
APIs use events internally for various pieces of functionality. To prevent conflicts, it is strongly suggested that you specify a custom user namespace—e.g., .myEvents
—when attaching your own handlers. It is further strongly suggested that you provide that same custom user namespace when removing them.
v2.28.0
: Introduced.See:
<jQuery>.off()
in the jQuery API docs for more information.
// Remove any handlers for the ended event.
someTracks.off('ended.myEvents');
<AudioRunner>.on(...args)
→ AudioRunner
object Attaches event handlers to the selected tracks. Returns a reference to the current AudioRunner
instance for chaining.
Note:
Shorthand for jQuery's .on()
method applied to each of the audio elements.
Warning:
The SimpleAudio
APIs use events internally for various pieces of functionality. To prevent conflicts, it is strongly suggested that you specify a custom user namespace—e.g., .myEvents
—when attaching your own handlers. It is further strongly suggested that you provide that same custom user namespace when removing them.
v2.28.0
: Introduced.See:
<jQuery>.on()
in the jQuery API docs for more information.
// Add a handler for the ended event.
someTracks.on('ended.myEvents', function () {
/* do something */
});
<AudioRunner>.one(...args)
→ AudioRunner
object Attaches single-use event handlers to the selected tracks. Returns a reference to the current AudioRunner
instance for chaining.
Note:
Shorthand for jQuery's .one()
method applied to each of the audio elements.
Warning:
The SimpleAudio
APIs use events internally for various pieces of functionality. To prevent conflicts, it is strongly suggested that you specify a custom user namespace—e.g., .myEvents
—when attaching your own handlers. It is further strongly suggested that you provide that same custom user namespace when removing them.
v2.28.0
: Introduced.See:
<jQuery>.one()
in the jQuery API docs for more information.
// Add a single-use handler for the ended event.
someTracks.one('ended.myEvents', function () {
/* do something */
});
<AudioRunner>.pause()
Pauses playback of the selected tracks.
v2.28.0
: Introduced.someTracks.pause();
<AudioRunner>.play()
Begins playback of the selected tracks.
v2.28.0
: Introduced.someTracks.play();
<AudioRunner>.playWhenAllowed()
Begins playback of the selected tracks or, failing that, sets the tracks to begin playback as soon as the player has interacted with the document.
v2.28.0
: Introduced.someTracks.playWhenAllowed();
<AudioRunner>.stop()
Stops playback of the selected tracks.
v2.28.0
: Introduced.someTracks.stop();
<AudioRunner>.time(seconds)
→ AudioRunner
object Sets the selected tracks' current time in seconds. Returns a reference to the current AudioRunner
instance for chaining.
v2.28.0
: Introduced.seconds
: (number
) The time to set. Valid values are floating-point numbers in the range 0
(start) to the maximum duration—e.g., 60
is 60
is sixty seconds in, 90.5
is ninety-point-five seconds in.// Set the selected tracks' current time to 30 seconds from their beginning.
someTracks.time(30);
<AudioRunner>.unload()
Stops playback of the selected tracks and forces them to drop any existing data.
Note: Once unloaded, playback cannot occur until the selected tracks' data is loaded again.
v2.28.0
: Introduced.someTracks.unload();
<AudioRunner>.volume(level)
→ AudioRunner
object Sets the selected tracks' volume level (default: 1
). Returns a reference to the current AudioRunner
instance for chaining.
v2.28.0
: Introduced.level
: (number
) The volume level to set. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.// Set the selected tracks' volume level to 75%.
someTracks.volume(0.75);
AudioList
API Audio lists (playlists) are useful for playing tracks in a sequence—i.e., one after another.
See Also:
SimpleAudio
API, AudioTrack
API, and AudioRunner
API.
<AudioList>.duration()
→ number
Returns the playlist's total playtime in seconds, Infinity
if it contains any streams, or NaN
if no metadata exists.
v2.28.0
: Introduced.var listLength = aList.duration();
<AudioList>.fade(duration , toVol [, fromVol])
→ Promise
Starts playback of the playlist and fades the currently playing track between the specified starting and destination volume levels over the specified number of seconds.
Note:
The Config.audio.pauseOnFadeToZero
setting (default: true
) determines whether the audio subsystem automatically pauses tracks that have been faded to 0
volume (silent).
v2.28.0
: Introduced.v2.29.0
: Updated to return a Promise
.duration
: (number
) The number of seconds over which the currently playing track should be faded.toVol
: (number
) The destination volume level.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the currently playing track's current volume level.// Fade the playlist from volume 0 to 1 over 6 seconds.
aList.fade(6, 1, 0);
<AudioList>.fadeIn(duration [, fromVol])
→ Promise
Starts playback of the playlist and fades the currently playing track from the specified volume level to 1
(loudest) over the specified number of seconds.
v2.28.0
: Introduced.v2.29.0
: Updated to return a Promise
.duration
: (number
) The number of seconds over which the currently playing track should be faded.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the currently playing track's current volume level.// Fade the playlist in from volume 0 over 5 seconds.
aList.fadeIn(5, 0);
<AudioList>.fadeOut(duration [, fromVol])
→ Promise
Starts playback of the playlist and fades the currently playing track from the specified volume level to 0
(silent) over the specified number of seconds.
Note:
The Config.audio.pauseOnFadeToZero
setting (default: true
) determines whether the audio subsystem automatically pauses tracks that have been faded to 0
volume (silent).
v2.28.0
: Introduced.v2.29.0
: Updated to return a Promise
.duration
: (number
) The number of seconds over which the currently playing track should be faded.fromVol
: (optional, number
) The starting volume level. If omitted, defaults to the currently playing track's current volume level.// Fade the playlist out from volume 1 over 8 seconds.
aList.fadeOut(8, 1);
<AudioList>.fadeStop()
Interrupts an in-progress fade of the currently playing track, or does nothing if no fade is progressing.
Note: This does not alter the volume level.
v2.29.0
: Introduced.aList.fadeStop();
<AudioList>.isEnded()
→ boolean
Returns whether playback of the playlist has ended.
v2.28.0
: Introduced.if (aList.isEnded()) {
/* do something */
}
<AudioList>.isFading()
→ boolean
Returns whether a fade is in-progress on the currently playing track.
v2.29.0
: Introduced.if (aList.isFading()) {
/* do something */
}
<AudioList>.isPaused()
→ boolean
Returns whether playback of the playlist has been paused.
v2.28.0
: Introduced.if (aList.isPaused()) {
/* do something */
}
<AudioList>.isPlaying()
→ boolean
Returns whether the playlist is playing.
v2.28.0
: Introduced.if (aList.isPlaying()) {
/* do something */
}
<AudioList>.isStopped()
→ boolean
Returns whether playback of the playlist has been stopped.
v2.29.0
: Introduced.if (aList.isStopped()) {
/* do something */
}
<AudioList>.load()
Pauses playback of the playlist and, if they're not already in the process of loading, forces its tracks to drop any existing data and begin loading.
Warning: This should not be done lightly if your audio sources are on the network, as it forces players to begin downloading them.
v2.28.0
: Introduced.aList.load();
<AudioList>.loop([state])
→ get: boolean
| set: AudioList
Gets or sets the playlist's repeating playback state (default: false
). When used to set the loop state, returns a reference to the current AudioList
instance for chaining.
v2.28.0
: Introduced.state
: (optional, boolean
) The loop state.// Get the playlist's current loop state.
var isLooped = aList.loop();
// Loop the playlist.
aList.loop(true);
// Unloop the playlist.
aList.loop(false);
<AudioList>.mute([state])
→ get: boolean
| set: AudioList
Gets or sets the playlist's volume mute state (default: false
). When used to set the mute state, returns a reference to the current AudioList
instance for chaining.
v2.28.0
: Introduced.state
: (optional, boolean
) The mute state.// Get the playlist's current volume mute state.
var isMuted = aList.mute();
// Mute the playlist's volume.
aList.mute(true);
// Unmute the playlist's volume.
aList.mute(false);
<AudioList>.pause()
Pauses playback of the playlist.
v2.28.0
: Introduced.aList.pause();
<AudioList>.play()
→ Promise
Begins playback of the playlist.
v2.28.0
: Introduced.v2.29.0
: Updated to return a Promise
.aList.play();
Promise
aList.play()
.then(function () {
console.log('The playlist is playing.');
})
.catch(function (problem) {
console.warn('There was a problem with playback: ' + problem);
});
<AudioList>.playWhenAllowed()
Begins playback of the playlist or, failing that, sets the playlist to begin playback as soon as the player has interacted with the document.
v2.29.0
: Introduced.aList.playWhenAllowed();
<AudioList>.remaining()
→ number
Returns how much remains of the playlist's total playtime in seconds, Infinity
if it contains any streams, or NaN
if no metadata exists.
v2.28.0
: Introduced.var listRemains = aList.remaining();
<AudioList>.shuffle([state])
→ get: boolean
| set: AudioList
Gets or sets the playlist's randomly shuffled playback state (default: false
). When used to set the shuffle state, returns a reference to the current AudioList
instance for chaining.
v2.28.0
: Introduced.state
: (optional, boolean
) The shuffle state.// Get the playlist's current shuffle state.
var isShuffled = aList.shuffle();
// Enable shuffling of the playlist.
aList.shuffle(true);
// Disable shuffling of the playlist.
aList.shuffle(false);
<AudioList>.skip()
Skips ahead to the next track in the playlist, if any.
v2.28.0
: Introduced.someTrack.skip();
<AudioList>.stop()
Stops playback of the playlist.
v2.28.0
: Introduced.someTrack.stop();
<AudioList>.time()
→ number
Returns the playlist's current time in seconds, or NaN
if no metadata exists.
v2.28.0
: Introduced.var listTime = aList.time();
<AudioList>.unload()
Stops playback of the playlist and forces its tracks to drop any existing data.
Note: Once unloaded, playback cannot occur until the track's data is loaded again.
v2.28.0
: Introduced.aList.unload();
<AudioList>.volume([level])
→ get: number
| set: AudioList
Gets or sets the playlist's volume level (default: 1
). When used to set the volume, returns a reference to the current AudioList
instance for chaining.
v2.28.0
: Introduced.level
: (optional, number
) The volume level to set. Valid values are floating-point numbers in the range 0
(silent) to 1
(loudest)—e.g., 0
is 0%, 0.5
is 50%, 1
is 100%.// Get the playlist's current volume level.
var trackVolume = aList.volume();
// Set the playlist's volume level to 75%.
aList.volume(0.75);
State
API The story history contains moments (states) created during play. Since it is possible to navigate the history—i.e., move backward and forward though the moments within the history—it may contain both past moments—i.e., moments that have been played—and future moments—i.e., moments that had been played, but have been rewound/undone, yet are still available to be restored.
In addition to the history, there is also the active moment—i.e., present—and expired moments—i.e., moments that had been played, but have expired from the history, thus cannot be navigated to.
API members dealing with the history work upon either the active moment—i.e., present—or one of the history subsets: the full in-play history—i.e., past + future—the past in-play subset—i.e., past only—or the extended past subset—i.e., expired + past. These instances will be noted.
State.active
→ Object
Returns the active (present) moment.
Note:
Using State.active
directly is generally unnecessary as there exist a number of shortcut properties, State.passage
and State.variables
, and story functions, passage()
and variables()
, which grant access to its normal properties.
v2.0.0
: Introduced.State.active.title → The title of the present moment
State.active.variables → The variables of the present moment
State.bottom
→ Object
Returns the bottommost (least recent) moment from the full in-play history (past + future).
v2.0.0
: Introduced.State.bottom.title → The title of the least recent moment within the full in-play history
State.bottom.variables → The variables of the least recent moment within the full in-play history
State.current
→ Object
Returns the current moment from the full in-play history (past + future), which is the pre-play version of the active moment.
Warning:
State.current
is not a synonym for State.active
. You will, very likely, never need to use State.current
directly within your code.
v2.8.0
: Introduced.State.current.title → The title of the current moment within the full in-play history
State.current.variables → The variables of the current moment within the full in-play history
State.length
→ integer number
Returns the number of moments within the past in-play history (past only).
v2.0.0
: Introduced.if (State.length === 0) {
/* No moments within the past in-play history. Egad! */
}
State.passage
→ string
Returns the title of the passage associated with the active (present) moment.
v2.0.0
: Introduced.State.passage → The passage title of the present moment
State.size
→ integer number
Returns the number of moments within the full in-play history (past + future).
v2.0.0
: Introduced.if (State.size === 0) {
/* No moments within the full in-play history. Egad! */
}
State.temporary
→ Object
Returns the current temporary variables.
v2.13.0
: Introduced.State.temporary → The current temporary variables
State.top
→ Object
Returns the topmost (most recent) moment from the full in-play history (past + future).
Warning:
State.top
is not a synonym for State.active
. You will, very likely, never need to use State.top
directly within your code.
v2.0.0
: Introduced.State.top.title → The title of the most recent moment within the full in-play history
State.top.variables → The variables of the most recent moment within the full in-play history
State.turns
→ integer number
Returns the total number (count) of played moments within the extended past history (expired + past).
v2.0.0
: Introduced.if (State.turns === 1) {
/* Initial turn. The starting passage is displayed. */
}
State.variables
→ Object
Returns the variables from the active (present) moment.
v2.0.0
: Introduced.State.variables → The variables of the present moment
State.getVar(varName)
→ any
Returns the value of the story or temporary variable by the given name.
v2.22.0
: Introduced.varName
: (string
) The name of the story or temporary variable, including its sigil—e.g., $charName
.State.getVar("$charName") → Returns the value of $charName
State.has(passageTitle)
→ boolean
Returns whether any moments with the given title exist within the past in-play history (past only).
Note:
State.has()
does not check expired moments. If you need to know if the player has ever been to a particular passage, then you must use the State.hasPlayed()
method or the hasVisited()
story function.
v2.0.0
: Introduced.passageTitle
: (string
) The title of the moment whose existence will be verified.State.has("The Ducky") → Returns whether a moment matching "The Ducky" exists
State.hasPlayed(passageTitle)
→ boolean
Returns whether any moments with the given title exist within the extended past history (expired + past).
Note:
If you need to check for multiple passages, the hasVisited()
story function will likely be more convenient to use.
v2.0.0
: Introduced.passageTitle
: (string
) The title of the moment whose existence will be verified.State.hasPlayed("The Ducky") → Returns whether a moment matching "The Ducky" ever existed
State.index(index)
→ Object
Returns the moment, relative to the bottom of the past in-play history (past only), at the given index.
v2.0.0
: Introduced.index
: (integer number
) The index of the moment to return.State.index(0) → Returns the least recent moment within the past in-play history
State.index(1) → Returns the second to least recent moment within the past in-play history
State.index(State.length - 1) → Returns the most recent moment within the past in-play history
State.isEmpty()
→ boolean
Returns whether the full in-play history (past + future) is empty.
v2.0.0
: Introduced.if (State.isEmpty()) {
/* No moments within the full in-play history. Egad! */
}
State.peek([offset])
→ Object
Returns the moment, relative to the top of the past in-play history (past only), at the, optional, offset.
v2.0.0
: Introduced.offset
: (optional, integer number
) The offset, from the top of the past in-play history, of the moment to return. If not given, an offset of 0
is used.State.peek() → Returns the most recent moment within the past in-play history
State.peek(0) → Returns the most recent moment within the past in-play history
State.peek(1) → Returns the second most recent moment within the past in-play history
State.peek(State.length - 1) → Returns the least recent moment within the past in-play history
State.metadata.size
→ integer number
Returns the size of the story metadata store—i.e., the number of stored pairs.
v2.30.0
: Introduced.// Determines whether the metadata store has any members.
if (State.metadata.size > 0) {
/* store is not empty */
}
State.metadata.clear()
Empties the story metadata store.
v2.29.0
: Introduced.// Removes all values from the metadata store.
State.metadata.clear();
State.metadata.delete(key)
Removes the specified key, and its associated value, from the story metadata store.
v2.29.0
: Introduced.key
: (string
) The key to delete.// Removes 'achievements' from the metadata store.
State.metadata.delete('achievements');
State.metadata.entries()
→ Array<Array<string, any>>
Returns an array of the story metadata store's key/value pairs as [key, value]
arrays.
v2.36.0
: Introduced.// Iterate over the pairs with a `for` loop.
var metadata = State.metadata.entries();
for (var i = 0; i < metadata.length; ++i) {
var key = metadata[i][0];
var value = metadata[i][1];
/* do something */
}
// Iterate over the pairs with `<Array>.forEach()`.
State.metadata.entries().forEach(function (pair) {
var key = pair[0];
var value = pair[1];
/* do something */
});
State.metadata.get(key)
→ any
Returns the value associated with the specified key from the story metadata store.
v2.29.0
: Introduced.key
: (string
) The key whose value should be returned.// Returns the value of 'achievements' from the metadata store.
var playerAchievements = State.metadata.get('achievements');
State.metadata.has(key)
→ boolean
Returns whether the specified key exists within the story metadata store.
v2.29.0
: Introduced.key
: (string
) The key whose existence should be tested.// Returns whether 'achievements' exists within the metadata store.
if (State.metadata.has('achievements')) {
/* do something */
}
State.metadata.keys()
→ Array<string>
Returns an array of the story metadata store's keys.
v2.36.0
: Introduced.// Iterate over the keys with a `for` loop.
var metadataKeys = State.metadata.keys();
for (var i = 0; i < metadataKeys.length; ++i) {
var key = metadataKeys[i];
/* do something */
}
// Iterate over the keys with `<Array>.forEach()`.
State.metadata.keys().forEach(function (key) {
/* do something */
});
State.metadata.set(key, value)
Sets the specified key and value within the story metadata store, which causes them to persist over story and browser restarts—n.b. private browsing modes do interfere with this. To update the value associated with a key, simply set it again.
Note: The story metadata, like saves, is tied to the specific story it was generated with. It is not a mechanism for moving data between stories.
Warning: The story metadata store is not, and should not be used as, a replacement for saves. Examples of good uses: achievement tracking, new game+ data, playthrough statistics, etc.
Warning: This feature is largely incompatible with private browsing modes, which cause all in-browser storage mechanisms to either persist only for the lifetime of the browsing session or fail outright.
v2.29.0
: Introduced.key
: (string
) The key that should be set.value
: (any
) The value to set.// Sets 'achievements', with the given value, in the metadata store.
State.metadata.set('achievements', { ateYellowSnow : true });
// Sets 'ngplus', with the given value, in the metadata store.
State.metadata.set('ngplus', true);
State.prng.init([seed [, useEntropy]])
Initializes the seedable pseudo-random number generator (PRNG) and integrates it into the story state and saves. Once initialized, the State.random()
method and story functions, random()
and randomFloat()
, return deterministic results from the seeded PRNG—by default, they return non-deterministic results from Math.random()
.
Note:
State.prng.init()
must be called during story initialization, within either your project's JavaScript section (Twine 2: the Story JavaScript; Twine 1/Twee: a script
-tagged passage) or the StoryInit
special passage. Additionally, it is strongly recommended that you do not specify any arguments to State.prng.init()
and allow it to automatically seed itself. If you should chose to use an explicit seed, however, it is strongly recommended that you also enable additional entropy, otherwise all playthroughs for all players will be exactly the same.
v2.29.0
: Introduced.seed
: (optional, string
) The explicit seed used to initialize the pseudo-random number generator.useEntropy
: (optional, boolean
) Enables the use of additional entropy to pad the specified explicit seed.State.prng.init() → Automatically seed the PRNG (recommended)
State.prng.init("aVeryLongSeed") → Seed the PRNG with "aVeryLongSeed" (not recommended)
State.prng.init("aVeryLongSeed", true) → Seed the PRNG with "aVeryLongSeed" and pad it with extra entropy
State.prng.isEnabled()
→ boolean
Returns whether the seedable PRNG has been enabled.
v2.29.0
: Introduced.State.prng.isEnabled() → Returns whether the seedable PRNG is enabled
State.prng.pull
→ integer number
| NaN
Returns the current pull count—i.e., how many requests have been made—from the seedable PRNG or, if the PRNG is not enabled, NaN
.
Note: The pull count is automatically included within saves and sessions, so this is not especially useful outside of debugging purposes.
v2.29.0
: Introduced.State.prng.pull → Returns the current PRNG pull count
State.prng.seed
→ string
| null
Returns the seed from the seedable PRNG or, if the PRNG is not enabled, null
.
Note: The seed is automatically included within saves and sessions, so this is not especially useful outside of debugging purposes.
v2.29.0
: Introduced.State.prng.seed → Returns the PRNG seed
State.random()
→ number
Returns a pseudo-random decimal number (floating-point) in the range 0
(inclusive) up to, but not including, 1
(exclusive).
Note:
By default, it simply returns non-deterministic results from Math.random()
, however, when the seedable PRNG has been enabled, via State.prng.init()
, it returns deterministic results from the seeded PRNG instead.
v2.0.0
: Introduced.State.random() → Returns a pseudo-random floating-point number in the range [0, 1)
State.setVar(varName, value)
→ boolean
Sets the value of the story or temporary variable by the given name. Returns whether the operation was successful.
v2.22.0
: Introduced.varName
: (string
) The name of the story or temporary variable, including its sigil—e.g., $charName
.value
: (any
) The value to assign.State.setVar("$charName", "Jane Doe") → Assigns the string "Jane Doe" to $charName
Story
API Story.id
→ string
The DOM-compatible ID of the story.
v2.37.0
: Introduced.The string
DOM-compatible ID of the story, created from the slugified story name.
Story.ifId
→ string
The IFID (Interactive Fiction IDentifier) of the story.
v2.5.0
: Introduced.The string
IFID of the story, or an empty string if no IFID exists. The Twine 2 ecosystem's IFIDs are v4 random UUIDs.
Story.name
→ string
The name of the story.
v2.37.0
: Introduced.The string
name of the story.
Story.add(descriptor)
→ boolean
Adds the passage to the passage store.
Note: This method cannot add code passages or passages tagged with code tags.
v2.37.0
: Introduced.descriptor
: (Object
) The passage descriptor object.A passage descriptor object should have the following properties:
name
: (string
) The passage's name.tags
: (string
) The passage's whitespace separated list of tags.text
: (string
) The passage's text.Boolean true
if the passage was added, elsewise false
.
// Add a passage
const descriptor = {
name : "Forest 4",
tags : "forest heavy",
text : "You can barely see farther than arm's length for all the trees.",
};
if (Story.add(descriptor)) {
/* The "Forest 4" passage was added. */
}
Story.delete(name)
→ boolean
Deletes the Passage
instance with the given name.
Note: This method cannot delete the starting passage, code passages, or passages tagged with code tags.
v2.37.0
: Introduced.name
: (string
) The name of the Passage
instance.Boolean true
if a Passage
instance with the given name was deleted, elsewise false
.
// Delete the Passage instance with the name "The Ducky"
if (Story.delete("The Ducky")) {
/* The "The Ducky" passage was deleted. */
}
Story.filter(predicate [, thisArg])
→ Array<Passage>
Searches all Passage
instances for those that pass the test implemented by the given predicate function.
Note: This method cannot retrieve passages tagged with code tags.
v2.37.0
: Introduced.predicate
: (Function
) The function used to test each Passage
instance, which is passed into the function as its sole parameter. If the function returns true
, then the Passage
instance is added to the results.thisArg
: (optional, any
) The value to use as this
when executing the predicate
function.A new Array<Passage>
filled with all instances that pass the test implemented by the given predicate function, or an empty Array
if no instances pass.
// Returns all 'forest'-tagged Passage instances
Story.filter(function (p) {
return p.tags.includes("forest");
});
// Returns all Passage instances whose names include whitespace
var hasWhitespaceRegExp = /\s/;
Story.filter(function (p) {
return hasWhitespaceRegExp.test(p.name);
});
Story.find(predicate [, thisArg])
→ Passage
Searches all Passage
instances for the first that passes the test implemented by the given predicate function.
Note: This method cannot retrieve passages tagged with code tags.
v2.37.0
: Introduced.predicate
: (Function
) The function used to test each Passage
object, which is passed into the function as its sole parameter. If the function returns true
, then the Passage
instance is added to the results.thisArg
: (optional, any
) The value to use as this
when executing the predicate
function.The first Passage
instance that passed the test implemented by the given predicate function, or undefined
if no instance passes.
// Returns the first 'forest'-tagged Passage instance
Story.find(function (p) {
return p.tags.includes("forest");
});
// Returns the first Passage instance whose name includes whitespace
var hasWhitespaceRegExp = /\s/;
Story.find(function (p) {
return hasWhitespaceRegExp.test(p.name);
});
Story.get(name)
→ Passage
Gets the Passage
instance with the given name.
Note: This method cannot retrieve passages tagged with code tags.
v2.0.0
: Introduced.name
: (string
) The name of the Passage
instance.The Passage
instance with the given name, or a new empty Passage
instance if no such passage exists.
// Get the Passage instance with the name "The Ducky"
const theDucky = Story.get("The Ducky");
Story.has(name)
→ boolean
Determines whether a Passage
instance with the given name exists.
Note: This method does not check passages tagged with code tags.
v2.0.0
: Introduced.name
: (string
) The name of the Passage
instance.Boolean true
if a Passage
instance with the given name exists, elsewise false
.
// Returns whether a "The Ducky" Passage instance exists
if (Story.has("The Ducky")) {
/* The "The Ducky" passage exists. */
}
Story.domId
→ string
Deprecated:
This setting has been deprecated and should no longer be used. See the Story.id
setting for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of Story.id
.Story.title
→ string
Deprecated:
This setting has been deprecated and should no longer be used. See the Story.name
setting for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of Story.name
.Story.lookup(propertyName , searchValue [, sortProperty])
→ Array<Passage>
Deprecated:
This static method has been deprecated and should no longer be used. See the Story.filter()
static method for its replacement.
v2.0.0
: Introduced.v2.37.0
: Deprecated in favor of Story.filter()
.Story.lookupWith(predicate [, sortProperty])
→ Array<Passage>
Deprecated:
This static method has been deprecated and should no longer be used. See the Story.filter()
static method for its replacement.
v2.11.0
: Introduced.v2.37.0
: Deprecated in favor of Story.filter()
.Template
API Template.size
→ number
Returns the number of existing templates.
v2.29.0
: Introduced.if (Template.size === 0) {
/* No templates exist. */
}
Template.add(name , definition)
Add new template(s).
v2.29.0
: Introduced.name
: (string
| Array<string>
) Name, or array of names, of the template(s) to add. NOTE: Names must consist of characters from the basic Latin alphabet and start with a letter, which may be optionally followed by any number of letters, numbers, the underscore, or the hyphen.definition
: (Function
| string
| Array<Function | string>
) Definition of the template(s), which may be a: function, string, or an array of either. NOTE: Each time array definitions are referenced, one of their member templates is randomly selected to be the output source.Function templates should return a string, which may itself contain markup. They are called with no arguments, but with their this
set to a template (execution) context object that contains the following data properties:
name
: (string
) The template's name.String templates consist solely of a string, which may itself contain markup.
/* Define a function template named ?yolo. */
Template.add('yolo', function () {
return either('YOLO', 'You Only Live Once');
});
/* Define a string template named ?nolf. */
Template.add('nolf', 'No One Lives Forever');
/* Define an array of string templates named ?alsoYolo. */
Template.add('alsoYolo', ['YOLO', 'You Only Live Once']);
/* Define an array of mixed string and function templates named ?cmyk. */
Template.add('cmyk', [
'Cyan',
function () {
return either('Magenta', 'Yellow');
},
'Black'
]);
this
)/* Define a function template with two names, ?color and ?Color, whose output changes based on its name. */
Template.add(['color', 'Color'], function () {
var color = either('red', 'green', 'blue');
return this.name === 'Color' ? color.toUpperFirst() : color;
});
Template.delete(name)
Remove existing template(s).
v2.29.0
: Introduced.name
: (string
| Array<string>
) Name, or array of names, of the template(s) to remove./* Deletes the template ?yolo. */
Template.delete('yolo');
/* Deletes the templates ?yolo and ?nolf. */
Template.delete(['yolo', 'nolf']);
Template.get(name)
→ Function
| string
| Array<Function | string>
Return the named template definition, or null
on failure.
v2.29.0
: Introduced.name
: (string
) Name of the template whose definition should be returned./* Returns the template ?yolo, or null if it doesn't exist. */
var yolo = Template.get('yolo');
Template.has(name)
→ boolean
Returns whether the named template exists.
v2.29.0
: Introduced.name
: (string
) Name of the template to search for.if (Template.has('yolo')) {
/* A ?yolo template exists. */
}
UI
API UI.alert(message [, options [, closeFn]])
Opens the built-in alert dialog, displaying the given message to the player.
v2.0.0
: Introduced.message
: (string
) The message to display to the player.options
: (optional, null
| Object
) The options object. See Dialog.open()
for more information.closeFn
: (optional, null
| Function
) The function to execute whenever the dialog is closed.UI.alert("You smell of elderberries!");
UI.restart([options [, closeFn]])
Opens the built-in restart dialog, prompting the player to restart the story.
v2.0.0
: Introduced.options
: (optional, null
| Object
) The options object. See Dialog.open()
for more information.closeFn
: (optional, null
| Function
) The function to execute whenever the dialog is closed.UI.restart();
UI.saves([options [, closeFn]])
Opens the built-in saves dialog.
v2.0.0
: Introduced.options
: (optional, null
| Object
) The options object. See Dialog.open()
for more information.closeFn
: (optional, null
| Function
) The function to execute whenever the dialog is closed.UI.saves();
UI.settings([options [, closeFn]])
Opens the built-in settings dialog, which is populated from the Setting
API.
v2.0.0
: Introduced.options
: (optional, null
| Object
) The options object. See Dialog.open()
for more information.closeFn
: (optional, null
| Function
) The function to execute whenever the dialog is closed.UI.settings();
UI.update()
Triggers a :uiupdate
event that causes the update of the dynamically updated sections built-in user interface—e.g., those populated by code passages, like StoryCaption
and StoryMenu
. Automatically invoked during passage navigation.
As all dynamically updated sections of the built-in UI are updated, save for the main passage display, it is recommended that this method be used sparingly.
Ideally, if you need to update these sections of the built-in UI outside of the normal passage navigation update, then you should update only the specific areas you need to rather than the entire UI.
v2.37.0
: Introduced.UI.update();
UI.jumpto([options [, closeFn]])
Deprecated: This method has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.UI.share([options [, closeFn]])
Deprecated: This method has been deprecated and should no longer be used.
v2.0.0
: Introduced.v2.37.0
: Deprecated.UIBar
API UIBar.destroy()
Completely removes the UI bar and all of its associated styles and event handlers.
v2.17.0
: Introduced.UIBar.destroy();
UIBar.hide()
→ UIBar
object Hides the UI bar. Returns a reference to the UIBar
object for chaining.
Note:
This does not reclaim the space reserved for the UI bar. Thus, a call to UIBar.stow()
may also be necessary. Alternatively, if you simply want the UI bar gone completely and permanently, either using UIBar.destroy()
or the StoryInterface
special passage may be a better choice.
v2.29.0
: Introduced.UIBar.hide();
UIBar.hide().stow();
UIBar.isHidden()
→ boolean
Returns whether the UI bar is currently hidden.
v2.29.0
: Introduced.if (UIBar.isHidden()) {
/* code to execute if the UI bar is hidden… */
}
if (!UIBar.isHidden()) {
/* code to execute if the UI bar is not hidden… */
}
UIBar.isStowed()
→ boolean
Returns whether the UI bar is currently stowed.
v2.29.0
: Introduced.if (UIBar.isStowed()) {
/* code to execute if the UI bar is stowed… */
}
if (!UIBar.isStowed()) {
/* code to execute if the UI bar is not stowed… */
}
UIBar.show()
→ UIBar
object Shows the UI bar. Returns a reference to the UIBar
object for chaining.
v2.29.0
: Introduced.UIBar.show();
UIBar.unstow().show();
UIBar.stow([noAnimation])
→ UIBar
object Stows the UI bar, so that it takes up less space. Returns a reference to the UIBar
object for chaining.
v2.17.0
: Introduced.v2.29.0
: Added returned UIBar
chaining reference.noAnimation
: (optional, boolean
) Whether to skip the default animation.UIBar.stow();
UIBar.stow(true);
UIBar.unstow([noAnimation])
→ UIBar
object Unstows the UI bar, so that it is fully accessible again. Returns a reference to the UIBar
object for chaining.
v2.17.0
: Introduced.v2.29.0
: Added returned UIBar
chaining reference.noAnimation
: (optional, boolean
) Whether to skip the default animation.UIBar.unstow();
UIBar.unstow(true);
UIBar.update()
Deprecated:
This method has been deprecated and should no longer be used. See the UI.update()
static method for its replacement.
v2.29.0
: Introduced.v2.37.0
: Deprecated in favor of UI.update()
.SugarCube preserves the state of the story as it's being played in a number of ways to both prevent the loss of progress and allow players to save stories. This guide will detail how these features work.
The story history is a collection of moments. A new moment is created whenever passage navigation occurs, and only when passage navigation occurs. Each moment contains data regarding the active passage and the state of all story variables—that is, the ones you use the $
sigil to interact with—as they exist when the moment is created. The history allows players to navigate through these moments.
The number of moments contained within the story history is, generally, limited, via the Config.history.maxStates
setting. As new moments are added, older moments that exceed the maximum number are expired in order of age, oldest first. Expired moments are recorded in a separate expired collection and can no longer be navigated to. If you limit the moments within the history to 1
, via setting Config.history.maxStates
to 1
, then there will only ever be one moment in the history, but passage navigation is still required for new moments to be created.
Note: All user functions and macros that check for the existence of moments within the history check both the story history and expired moments, so will work as expected even if the history is limited to a single moment as described above.
Saving the story records the story's state up until the last moment that was created. This is not necessarily the same as the current state of the story: because moment creation is tied to passage navigation, changes that occur between one passage navigation and the next are not part of the current moment and will not be preserved by a moment until the next navigation, when the next moment is created.
Consider the following:
:: one passage
<<set $var to 1>>
[[another passage]]
:: another passage
<<link "Click me!">>
<<set $var to 2>>
<</link>>
In the above example, if you save the story after reaching the passage called another passage
, the $var
variable will be saved in the state as 1
, as you would expect. If you click the link that sets the variable to 2
, and then save the story, the $var
variable will still be saved as 1
, because a new moment has not yet been created.
Note: Auto saves are occasionally confused with the playthrough session, but they are in fact distinct systems.
SugarCube automatically stores the current playthrough state to the browser's session storage whenever a new moment is created. This can be thought of as a special, temporary saved story, which is automatically deleted after the player's current browsing session ends. This temporary playthrough session is intended to prevent players from losing data. Some browsers, particularly mobile ones, will free up memory by unloading web pages that are running in the background. This functionally refreshes the webpage, and can cause users to lose their progress. When SugarCube is reloaded by the browser, it checks if a playthrough session exists and loads it to prevent any inadvertent loss of progress.
This feature also prevents players from losing progress if they try to use the browser back and forward buttons to navigate, or if they refresh their browser for any reason. The built-in Restart
button, along with the methods UI.restart()
and Engine.restart()
are provided so that the story can be restarted without restoring a session.
To recap:
Note: A playthrough session is occasionally confused with auto saves, but they are in fact distinct systems.
SugarCube features configurable auto saves. Auto saves are otherwise normal browser saves that automatically save either on every turn or only on certain turns, depending on how they're configured.
See:
Config.saves.maxAutoSave
setting, Config.saves.isAllowed
setting, and Save.browser.auto
API.
When a save is loaded, the state loaded from the save replaces the current state. This process is the same regardless of where the loaded state is coming from, be it a save or the playthrough session. The previous state is completely lost—the new state is not added to or combined with the current state, instead it replaces it in its entirety. The easiest way to understand this is to look at what happens when you make some changes to StoryInit
and then load a save from before those changes were made. For example:
:: StoryInit
<<set $x to 0>>
:: Start
$$x is <<if def $x>> $x <<else>> undefined <</if>>
If you run the above, you'll see $x is 0
. Create a save, then edit the code as follows:
:: StoryInit
<<set $x to 0>>
<<set $y to 1>>
:: Start
$$x is <<if def $x>> $x <<else>> undefined <</if>>
$$y is <<if def $y>> $y <<else>> undefined <</if>>
Running that, you'll see $x is 0
and $y is 1
. Now, load the save from before the changes were made, and you'll see $y is undefined
, since it doesn't exist at all in the loaded state.
Whenever your story is first started or, for any reason, restarted—e.g., the browser window/tab was refreshed/reloaded—it undergoes its startup sequence. Several things occur each and every time startup happens, regardless of whether or not a playthrough session will be restored or the starting passage run. First, the CSS, JavaScript, and Widget sections are processed. Next, any init
-tagged passages and the StoryInit
special passage are processed. Finally, one of two things happen (in order): the existing playthrough session is restored, if it exists, elsewise the starting passage is run.
Some users have the false impression that StoryInit
is not run when the story is restarted when the playthrough session is restored or autosave is loaded. Code like <<set $y to 1>>
seems to have no effect because the startup state is replaced by the of the incoming state, but they are still executed by the engine. You can see this effect by changing data outside the state. For example, let's return to the example above and change it again:
:: StoryInit
<<set $x to 0>>
<<set setup.y to 1>>
:: Start
$$x is <<if def $x>> $x <<else>> undefined <</if>>
setup.y is <<if def setup.y>> <<= setup.y>> <<else>> undefined <</if>>
You'll see that setup.y
is being set to 1
and displayed properly regardless of whether you load a save or not, because it is not part of the state.
When the story is restarted by SugarCube rather than refreshed via the browser, the playthrough session, if any, is not loaded. The CSS, JavaScript, & Widget sections, any init
-tagged passages, and the StoryInit
special passage are processed, as usual, and then the starting passage is rendered.
As a basic working definition, non-generic object types—i.e., classes—are instantiable objects whose own prototype is not Object
—e.g., Array
is a native non-generic object type.
Many of the commonly used native non-generic object types are already fully compatible with and supported for use within story variables—e.g., Array
, Date
, Map
, and Set
. All other non-generic object types, on the other hand, must be made compatible to be successfully stored within story variables.
Making custom non-generic object types fully compatible requires that two methods be added to their prototype, .clone()
and .toJSON()
, to support cloning—i.e., deep copying—instances of the type.
.clone()
method needs to return a clone of the instance..toJSON()
method needs to return a code string that when evaluated will return a clone of the instance.In both cases, since the end goal is roughly the same, this means creating a new instance of the base object type and populating it with clones of the original instance's data. There is no one size fits all example for either of these methods because an instance's properties, and the data contained therein, are what determine what you need to do.
See Also:
The Serial.createReviver()
method for additional information on implementing the .toJSON()
method.
class
-based syntax (newer, preferred) Here's a simple example whose constructor takes a single configuration object parameter:
window.Character = class Character {
constructor(config) {
// Set up our own data properties with some defaults.
this.name = '(none)';
this.race = '(none)';
this.st = 10;
this.dx = 10;
this.iq = 10;
this.ht = 10;
this.hp = 10;
// Clone the given config object's own properties into our own properties.
//
// NOTE: We use the SugarCube built-in `clone()` function to make deep
// copies of each of the properties' values.
Object.keys(config).forEach((pn) => {
this[pn] = clone(config[pn]);
});
}
clone() {
// Return a new instance containing our own data.
return new this.constructor(this);
}
toJSON() {
// Return a code string that will create a new instance containing our
// own data.
//
// NOTE: Supplying `this` directly as the `reviveData` parameter to the
// `Serial.createReviver()` call will trigger out of control recursion in
// the serializer, so we must pass it a clone of our own data instead.
const ownData = {};
Object.keys(this).forEach((pn) => {
ownData[pn] = clone(this[pn]);
});
return Serial.createReviver(`new ${this.constructor.name}($ReviveData$)`, ownData);
}
};
Creating a new instance of this Character
example would be something like:
<<set $Joe to new Character({
name : 'Joe the Barbarian',
race : 'human',
st : 20,
dx : 12,
iq : 9,
ht : 18,
hp : 18
})>>
Here's a simple example whose constructor takes multiple discrete parameters:
window.Character = class Character {
constructor(
name,
race,
st,
dx,
iq,
ht,
hp
) {
// Set up our own data properties with the given values or defaults.
this.name = name ?? '(none)';
this.race = race ?? '(none)';
this.st = st ?? 10;
this.dx = dx ?? 10;
this.iq = iq ?? 10;
this.ht = ht ?? 10;
this.hp = hp ?? 10;
}
clone() {
// Return a new instance containing our own data.
return new this.constructor(
this.name,
this.race,
this.st,
this.dx,
this.iq,
this.ht,
this.hp
);
}
toJSON() {
// Return a code string that will create a new instance containing our
// own data.
return Serial.createReviver(String.format(
'new {0}({1},{2},{3},{4},{5},{6},{7})',
this.constructor.name,
JSON.stringify(this.name),
JSON.stringify(this.race),
JSON.stringify(this.st),
JSON.stringify(this.dx),
JSON.stringify(this.iq),
JSON.stringify(this.ht),
JSON.stringify(this.hp)
));
}
};
Creating a new instance of this Character
example would be something like:
<<set $Joe to new Character(
'Joe the Barbarian',
'human',
20,
12,
9,
18,
18
)>>
function
-based syntax (classic, not recommended) Here's a simple example whose constructor takes a single configuration object parameter:
window.Character = function Character(config) {
// Set up our own data properties with some defaults.
this.name = '(none)';
this.race = '(none)';
this.st = 10;
this.dx = 10;
this.iq = 10;
this.ht = 10;
this.hp = 10;
// Clone the given config object's own properties into our own properties.
//
// NOTE: We use the SugarCube built-in `clone()` function to make deep
// copies of each of the properties' values.
Object.keys(config).forEach(function (pn) {
this[pn] = clone(config[pn]);
}, this);
};
Character.prototype.clone = function () {
// Return a new instance containing our own data.
return new Character(this);
};
Character.prototype.toJSON = function () {
// Return a code string that will create a new instance containing our
// own data.
//
// NOTE: Supplying `this` directly as the `reviveData` parameter to the
// `Serial.createReviver()` call will trigger out of control recursion in
// the serializer, so we must pass it a clone of our own data instead.
var ownData = {};
Object.keys(this).forEach(function (pn) {
ownData[pn] = clone(this[pn]);
}, this);
return Serial.createReviver('new Character($ReviveData$)', ownData);
};
Creating a new instance of this Character
example would be something like:
<<set $Joe to new Character({
name : 'Joe the Barbarian',
race : 'human',
st : 20,
dx : 12,
iq : 9,
ht : 18,
hp : 18
})>>
Here's a simple example whose constructor takes multiple discrete parameters:
window.Character = function (
name,
race,
st,
dx,
iq,
ht,
hp
) {
// Set up our own data properties with the given values or defaults.
this.name = name || '(none)';
this.race = race || '(none)';
this.st = st || 10;
this.dx = dx || 10;
this.iq = iq || 10;
this.ht = ht || 10;
this.hp = hp || 10;
};
Character.prototype.clone = function () {
// Return a new instance containing our own data.
return new Character(
this.name,
this.race,
this.st,
this.dx,
this.iq,
this.ht,
this.hp
);
};
Character.prototype.toJSON = function () {
// Return a code string that will create a new instance containing our
// own data.
return Serial.createReviver(String.format(
'new Character({0},{1},{2},{3},{4},{5},{6})',
JSON.stringify(this.name),
JSON.stringify(this.race),
JSON.stringify(this.st),
JSON.stringify(this.dx),
JSON.stringify(this.iq),
JSON.stringify(this.ht),
JSON.stringify(this.hp)
));
};
Creating a new instance of this Character
example would be something like:
<<set $Joe to new Character(
'Joe the Barbarian',
'human',
20,
12,
9,
18,
18
)>>
This is a collection of tips, from how-tos to best practices.
Suggestions for new entries may be submitted by creating a new issue at SugarCube's source code repository.
Warning:
Navigating back to a previous passage, for whatever reason, can be problematic. There's no way for the system to know ahead of time whether it's safe to re-execute a passage's contents. Even if it did know that, there's no way for it to know which operations may or may not have side-effects—e.g., changing variables. Thus, if you allow players to return to passages, then you should either: ensure the passages contain no code that has side-effects or wrap that code in something to prevent re-execution—e.g., <<if visited() is 1>>side-effects<</if>>
.
Note:
An alternative to navigating to passages to create menus, inventories, and the like would be to use the Dialog
API.
When you have a situation where you're using a set of passages as some kind of menu/inventory/etc and it's possible for the player to interact with several of those passages, or even simply the same one multiple times, then returning them to the passage they were at before entering the menu can be problematic as they're possibly several passages removed from that originating passage—thus, the <<return>>
macro and link constructs like [[Return|previous()]]
will not work.
The most common way to resolve this arbitrarily long return issue is to use a bit of JavaScript to record the last non-menu passage the player visited into a story variable and then to create a link with that.
For instance, you may use one of the following examples—they both do the same thing—to record the last non-menu passage into the $return
story variable.
Via JavaScript (Twine 2: the Story JavaScript, Twine 1/Twee: a script
-tagged passage)
$(document).on(':passagestart', function (ev) {
if (!ev.passage.tags.includes('noreturn')) {
State.variables.return = ev.passage.name;
}
});
Via macros (best used in the PassageReady
special passage)
<<if not tags().includes("noreturn")>>
<<set $return to passage()>>
<</if>>
You'll need to tag each and every one of your menu passages with noreturn
—you may use any tag you wish (e.g., menu
, inventory
), just ensure you change the name in the code if you decide upon another. If necessary, you may also use multiple tags by switching from <Array>.includes()
to <Array>.includesAny()
in whichever of the above examples you choose to use.
In your menu passages, your long return links will simply reference the $return
story variable, like so:
→ Using link markup
[[Return|$return]]
→ Using <<link>> macro (separate argument form)
<<link "Return" $return>><</link>>
Warning (Twine 2):
Due to how the Twine 2 automatic passage creation feature currently works, using the link markup form will cause a passage named $return
to be created that will need to be deleted. To avoid this problem, it's suggested that you use the separate argument form of the <<link>>
macro in Twine 2—as shown above.
Media passages are simply a way to embed media into your project—specially tagged passages that contain the data URI of a Base64-encoded media source. Audio, image, video, and VTT passages are supported.
For example, the following is the data URI of a Base64-encoded PNG image of a red dot ():
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHE
lEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==
v2.0.0
: Image passages.v2.24.0
: Added audio, video, and VTT passages.Generally, it's expected that you will use a compiler that supports the automatic creation of media passages, however, they may be created manually.
Compilers supporting automatic creation of media passages:
Warning (Twine 2): Due to various limitations in its design, if you're using Twine 2 as your IDE/compiler, then it is strongly recommended that you do not create more than a few media passages and definitely do not use large sources.
To manually create a media passage:
See the MDN article Media formats for HTML audio and video for more information on formats commonly supported in browsers—pay special attention to the Browser compatibility section.
Note: As with all special tags, media passage tags are case sensitive, so their spelling and capitalization must be exactly as shown.
Passage type | Tag |
---|---|
Audio passage | Twine.audio |
Image passage | Twine.image |
Video passage | Twine.video |
VTT passage | Twine.vtt |
This guide is a reference to the icon font used by SugarCube, sc-icons
, which is a subset of Font Awesome Free 6.7.2
.
Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com
License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
Copyright 2024 Fonticons, Inc.
The following CSS properties should be used with any icon style rule.
cursor: pointer;
display: inline-block;
font-family: sc-icons !important;
font-size: 1em;
font-style: normal;
font-variant: normal;
font-weight: 900;
line-height: 1;
speak: never;
text-decoration: none;
text-rendering: auto;
text-transform: none;
user-select: none;
/* vendor-prefixed properties */
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
Each icon's hexadecimal reference ID is listed below it. How to use the reference IDs varies based on where you're using them.
\u
. E.g., \uf004
.\
. E.g., \f004
.&#x
and ;
. E.g., 
.e0b7
e2ca
e447
e490
e68f
f002
f004
f005
f007
f00c
f00d
f00e
f010
f011
f013
f015
f019
f01e
f021
f023
f026
f027
f028
f02b
f02c
f02e
f031
f032
f033
f034
f035
f036
f037
f038
f039
f03a
f03b
f03c
f03d
f03e
f042
f047
f048
f049
f04a
f04b
f04c
f04d
f04e
f050
f051
f052
f053
f054
f055
f056
f057
f058
f059
f05a
f05e
f060
f061
f062
f063
f065
f066
f067
f068
f06a
f06e
f070
f071
f074
f076
f077
f078
f079
f07d
f07e
f089
f08b
f08d
f08e
f090
f093
f09c
f09e
f0a0
f0a8
f0a9
f0aa
f0ab
f0ad
f0b2
f0c1
f0c2
f0c4
f0c5
f0c7
f0c9
f0ca
f0cb
f0cc
f0cd
f0d0
f0d7
f0d8
f0d9
f0da
f0dc
f0dd
f0de
f0e2
f0e7
f0ea
f0eb
f0ec
f0ed
f0ee
f0f3
f0fe
f100
f101
f102
f103
f104
f105
f106
f107
f10d
f10e
f110
f120
f121
f125
f127
f12b
f12c
f130
f131
f135
f137
f138
f139
f13a
f13e
f141
f142
f143
f144
f146
f148
f149
f14a
f14c
f150
f151
f152
f15d
f15e
f160
f161
f162
f163
f164
f165
f175
f176
f177
f178
f185
f186
f188
f191
f192
f1ab
f1c0
f1dc
f1dd
f1de
f1e0
f1f6
f1f8
f1fb
f204
f205
f20a
f240
f241
f242
f243
f244
f245
f246
f249
f24d
f28b
f28d
f29e
f2a2
f2a4
f2a8
f2d0
f2d1
f2d2
f2d3
f2ea
f2ed
f2f1
f2f5
f2f6
f2f9
f304
f309
f30a
f30b
f30c
f31e
f337
f338
f358
f359
f35a
f35b
f35d
f360
f362
f363
f3be
f3bf
f3c1
f3e0
f410
f422
f424
f4e2
f522
f534
f53f
f54a
f55a
f565
f56d
f56e
f56f
f574
f58f
f590
f5c0
f6a9
f715
f78c
f7a4
f7a5
f7a9
f7d9
f829
f82a
f84c
f850
f853
f87d
f881
f882
f884
f885
f886
f887
f891
There are many differences between Harlowe and SugarCube, this guide will document some of the most critical you will need to account for if you're coming to SugarCube from a background in Harlowe.
Aside from general syntax, SugarCube macros do not use hooks, separate arguments differently, and don't allow other macros to be passed as arguments.
Like in Harlowe, some SugarCube macros accept expressions and others accept discreet arguments. In SugarCube, discreet arguments passed to a macro are separated by spaces instead of commas. To pass expressions or the results of functions to macros as an argument, you must wrap the expression in backquotes (`
).
Additionally, macros in SugarCube do not return values, so macros cannot be used as arguments to other macros. SugarCube provides a variety of functions and methods that may be used instead, and standard JavaScript functions and methods may also be used.
Consider the following Harlowe code:
(link-goto: "Go somewhere else", (either: "this passage", "that passage", "the other passage"))
A version of the above code in SugarCube might look like this:
<<link "Go somewhere else" `either("this passage", "that passage", "the other passage")`>><</link>>
See: Macro Arguments.
Where Harlowe uses its hook syntax (square brackets) to associate a macro with its contents, SugarCube instead uses "container" macros—macros that can have content associated with them have opening and closing tags.
Consider the following Harlowe code:
(if: $var is 1)[
The variable is 1.
]
In SugarCube, you instead open and close the <<if>>
macro itself:
<<if $var is 1>>
The variable is 1.
<</if>>
Some macros in Harlowe and SugarCube share a name but work a bit differently. We'll cover some of these differences below.
SugarCube does not have any equivalents to Harlowe's (click:)
family of macros. Additionally, SugarCube's normal <<link>>
macro does not have an output element associated with it and is not, by default, a single-use link like its Harlowe equivalent. Both of these features can be constructed in SugarCube, however, using macros like <<linkreplace>>
or by combining <<link>>
macros with DOM macros. Additionally, SugarCube's link macro accepts a passage argument, that, if included, turns any <<link>>
into something similar to Harlowe's (link-goto:)
macro.
Consider the following Harlowe link macros:
(link: "Hey there.")[Hay is for horses.]
(link-repeat: "Get some money")[(set: $cash to it + 1)]
(link-goto: "Move on", "next passage")
The equivalent SugarCube code for each link might look something like this:
<<linkreplace "Hey there.">>Hay is for horses.<</linkreplace>>
<<link "Get some money">><<set $cash += 1>><</link>>
<<link "Move on" "next passage">><</link>>
SugarCube's <<link>>
and <<button>>
macros can also accept the link markup as an argument:
<<link [[Move on|next passage]]>><</link>>
Note: Harlowe refers to these as "revision macros".
SugarCube's DOM macros can target any HTML element on the page, not just hooks, and unlike their Harlowe equivalents, they cannot target arbitrary strings. You can use custom style markup or HTML to create the elements, and then target them with a query selector.
Consider the following Harlowe code:
(set: _greetings to (a: "hi", "hello", "good day", "greetings"))\
The man says, "|target>[(either: ..._greetings)]."
{
(link-repeat: "Change")[
(replace: ?target)[(either: ..._greetings)]
]
}
The equivalent SugarCube code to achieve a similar result would be:
<<set _greetings to ["hi", "hello", "good day", "greetings"]>>\
The man says, "@@#target;<<= _greetings.random()>>@@."
<<link "Change">>
<<replace "#target">><<= _greetings.random()>><</replace>>
<</link>>
Note: The DOM macros do have a limitation that you should familiarize yourself with.
Harlowe's implementation of the (goto:)
macro terminates the rendering passage. In SugarCube, the passage is not terminated, and anything in the code below the <<goto>>
macro will have side effects.
Consider this Harlowe code:
:: some passage
(set: $count to 0)
(goto: "next")
(set: $count to it + 1)
:: next
$count <!-- 0 -->
In the above, the second (set:)
macro is never run, and the $count
variable remains at 0.
The equivalent SugarCube code works a bit differently:
:: some passage
<<set $count to 0>>
<<goto "next">>
<<set $count += 1>>
:: next
$count /* 1 */
SugarCube does not terminate the parsing of the calling passage, so some care is required when calling <<goto>>
.
As with <<link>>
and <<button>>
, <<goto>>
can accept link markup as its argument:
<<goto [[next]]>>
SugarCube's user input macros, like <<textbox>>
, cannot be nested inside a <<set>>
macro, as you might do with a (prompt:)
and a (set:)
in Harlowe. Instead, the macro is passed a receiver variable which is set to the value input by the user.
For example, if you wanted to ask the user to enter a name, your code may look like this in Harlowe:
(set: $name to (prompt: "What is your name?", "Frank"))
In SugarCube, you would likely want to use the <<textbox>>
macro instead, and pass $name
in as the receiving variable:
<label>What is your name? <<textbox "$name" "Frank">></label>
Harlowe's newer input macros, like (dropdown:)
and (cycling-link:)
use "bound" variables, which are similar in concept to SugarCube's receiver variables.
Harlowe's implementation of data types differs significantly from SugarCube's. A data type refers to the "type" of data a variable is holding, such as a number, a string, an array, or anything else. Harlowe has stricter typing than SugarCube, requiring authors to call macros like (str:)
or (num:)
on variables to change their type. SugarCube, like JavaScript, uses dynamic typing.
SugarCube, like JavaScript, will try to make sense of expressions passed to it by coercing their values if necessary:
<<set $number to 1>>
<<set $string to "2">>
<<= $string + $number>> /* "21" */
In the above case, since the string value "2"
cannot be added to a number value, the number value is coerced into a string, and the two strings are then concatenated. In Harlowe, the same operation will yield an error:
(set: $number to 1)
(set: $string to "2")
(print: $string + $number) <!-- error! -->
You must convert the values to the same type in Harlowe. In SugarCube you can convert them if you need to.
In Harlowe:
(set: $number to 1)
(set: $string to "2")
(print: $string + $number) <!-- error! -->
(print: $string + (str: $number)) <!-- "21" -->
(print: (num: $string) + $number) <!-- 3 -->
In SugarCube:
<<set $number to 1>>
<<set $string to "2">>
<<= $string + $number>> /* "21" */
<<= $string + String($number)>> /* "21" */
<<= Number($string) + $number>> /* 3 */
Harlowe's arrays, datamaps, and datasets are functionally similar to JavaScript Array
s, Map
s, and Set
s, but with a few key differences. SugarCube requires authors to define and work with these data types using the standard JavaScript methods rather than providing macros for them.
Using an array in Harlowe:
(set: $array to (a:))
(set: $array to it + (a: "something"))
(if: $array contains "something")[…]
In SugarCube:
<<set $array to []>>
<<run $array.push("something")>>
<<if $array.includes("something")>>…<</if>>
Using a datamap in Harlowe:
(set: $map to (dm: "key", "value"))
(set: $map's key to "another value")
(if: $map contains key)[…]
In SugarCube:
<<set $map to new Map([["key", "value"]])>>
<<run $map.set("key", "another value")>>
<<if $map.has("key")>>…<</if>>
SugarCube also allows the use of JavaScript generic objects, which may be better in some situations than a map:
<<set $object to { key : "value" }>>
<<set $object.key to "another value">>
<<if $object.hasOwnProperty("key")>>…<</if>>
Another important difference in the way Harlowe handles its non-primitive data types like arrays, datamaps, and datasets is that they are passed by value rather than passed by reference.
Consider the following Harlowe code:
(set: $player to (dm: "hp", 100, "mp", 50))
(set: $partyMember to $player)
(set: $partyMember's hp to it - 50)
(print: $player's hp) <!-- 100 -->
(print: $partyMember's hp) <!-- 50 -->
As you can see, Harlowe creates a deep copy/clone of its non-primitive data types each time they're modified.
In SugarCube, both variables would still point to the same underlying object—at least initially (see below):
<<set $player to { hp : 100, mp : 50 }>>
<<set $partyMember to $player>>
<<set $partyMember.hp -= 50>>
$player.hp /* 50 */
$partyMember.hp /* 50 */
SugarCube does eventually clone its non-primitive data types as well, but does at the start of passage navigation, rather than each time they're modified.
v2.2.0
: Introduced.In test mode, SugarCube will wrap all macros, and some non-macro markup—e.g., link & image markup—within additional HTML elements, called "debug views" ("views" for short). Views make their associated code visible, thus providing onscreen feedback—they may also be hovered over which, generally, exposes additional information about the underlying code.
Warning: Because of the additional HTML elements added by the debug views, some nested markup and selectors may be broken. This only affects test mode.
Tip: In versions of SugarCube ≥v2.23.0, the debugging interface offers additional tools, namely variable watches and arbitrary history navigation.
To enable test mode, use the test option (-t
, --test
).
To enable test mode from the Stories screen, click on the story's gear menu and select the Test Story menu item.
To enable test mode from the story editor/map screen, click on the Test menu item (right side of the bottom bar).
To enable test mode from the story editor/map screen while starting at a specific passage, hover over a passage and select the menu item.
To enable test mode from the Stories screen, click on the story's gear menu and select the Test Play menu item.
To enable test mode from the story editor/map screen, click on the Test menu item (right side of the bottom bar).
To enable test mode from the story editor/map screen while starting at a specific passage, hover over a passage and select the menu item.
To enable test mode while starting at a specific passage, right-click on a passage and select the Test Play From Here context menu item.
Note:
Unfortunately, due to limitations in the current release of Twine 1, the Build menu's Test Play menu item is not able to trigger test mode. You may, however, simply use the Test Play From Here context menu item on the Start
passage to achieve the same result.
You may forcibly enable test mode manually by setting the Config
object's debug
property to true
. For example:
Config.debug = true; // forcibly enable test mode
See:
The Config.debug
setting for more information.
The debug bar (bottom right corner of the page) allows you to: watch the values of story and temporary variables, toggle the debug views, and jump to any moment/turn within the history.
The variable watch panel may be toggled via the Watch button. To add a watch for a variable, type its name into the Add field and then either press enter/return or click the button—n.b. depending on the age of your browser, you may also see a list of all current variables when interacting with the Add field. To delete a watch, click the button next to its name in the watch panel. To add watches for all current variables, click the button. To delete all current watches, click the button.
The debug views may be toggled via the Views button.
To jump to any moment/turn within the available history, select the moment/turn from the Turn select field.
The debug views themselves may be toggled on and off (default: on) via the Debug View button (top of the UI bar).
If you've removed/hidden the UI bar, a construct like the following will allow you to toggle the views on and off:
<<button "Toggle Debug Views">><<script>>DebugView.toggle()<</script>><</button>>
Note: That will only toggles the views, test mode must still be enabled first.
TypeScript bindings for SugarCube APIs can found as the Definitely Typed package: @types/twine-sugarcube
.
To install the package via NPM, use the following command:
npm install --save-dev @types/twine-sugarcube
This is a reference on how to install SugarCube in Tweego, Twine 2, and Twine 1/Twee.
Note (Twine 2): Newer versions of Twine 2 come bundled with a version of SugarCube v2, so you only need to read these instructions if you want to install a newer version of SugarCube v2 than is bundled or a non-standard release.
See Tweego's documentation for more information.
There are two primary branches of Twine 2 as far as SugarCube is concerned:
Regardless of the version of Twine 2 you're using, follow these instructions to install a local copy of SugarCube v2:
Formats
link in the Twine 2 sidebar.Add a New Format
tab.format.js
file, based upon the path from step #2, into the textbox and click the +Add
button (see below for examples).Note: If constructing the file URL from a shell path, ensure that either it does not contain escapes or you properly convert them into the correct URL percent-encoded form.
If the full path to the contents of the archive is something like:
/home/soandso/Twine/StoryFormats/SugarCube-2/format.js
Then the file URL to it would be:
file:///home/soandso/Twine/StoryFormats/SugarCube-2/format.js
If the full path to the contents of the archive is something like:
C:\Users\soandso\Documents\Twine\StoryFormats\SugarCube-2\format.js
Then the file URL to it would be (note the changed slashes):
file:///C:/Users/soandso/Documents/Twine/StoryFormats/SugarCube-2/format.js
The online SugarCube install, delivered by the jsDelivr CDN, supports only versions of Twine 2 ≥2.1.
Copy the following URL and paste it into the Add a New Format tab of the Formats menu, from Twine 2's sidebar.
URL: https://cdn.jsdelivr.net/gh/tmedwards/sugarcube-2/dist/format.js
Follow these instructions to install a local copy of SugarCube v2:
targets
directory within.targets
directory and extract it, included directory and all.If you followed the steps correctly, within Twine 1/Twee's targets
directory you should now have a sugarcube-2
directory, which contains several files—e.g., header.html
, sugarcube-2.py
, etc.
Due to a flaw in the current release of Twine 1/Twee (v1.4.2
), if you rename the directory included in the archive (or simply copy its contents to your current SugarCube v2 install), then you must ensure that the file with the extension .py
(the story format's custom Twine 1 Header class file) within is named the same as the directory—i.e., the name of the directory and .py
file must match.
For example, if the name of SugarCube's directory is sugarcube
, then the name of the .py
file within must be sugarcube.py
. Similarly, if the directory is sugarcube-2
, then the name of the .py
file within must be sugarcube-2.py
. Etc.
The directory and .py
file names within the archive available for download are already properly matched—as sugarcube-2
and sugarcube-2.py
—and to avoid issues it recommended that you simply do not rename them.
This is a reference on how to update existing SugarCube code to work with newer versions of SugarCube.
Note: The majority of newer SugarCube versions do not have any changes that would require an update. For those versions that do, the updates are normally completely elective and may be addressed at your leisure, or not at all. Sometimes there are breaking changes, however, and these must be addressed immediately.
Warning: Some changes within this version are breaking changes that you must address immediately, while others are elective changes that you may address at your leisure. All breaking changes will be so noted.
Note:
The removals herein are of features that have been deprecated for years. Most are v1
compatibility APIs that have always been deprecated in v2
. Nothing of value has been lost.
API | Change |
---|---|
browser |
BREAKING: This deprecated legacy API has been removed. Its replacement is Browser . |
config |
BREAKING: This deprecated legacy API has been removed. Its replacement is Config . |
has |
BREAKING: This deprecated legacy API has been removed. Its replacement is Has . |
History |
BREAKING: This deprecated legacy API has been removed. Its replacement is State . |
state |
BREAKING: This deprecated legacy API has been removed. Its replacement is State . |
tale |
BREAKING: This deprecated legacy API has been removed. Its replacement is Story . |
TempVariables |
BREAKING: This deprecated legacy API has been removed. Its replacement is State.temporary . |
Array
APIMethod | Change |
---|---|
Array.random() |
BREAKING: This deprecated static method has been removed. See the <Array>.random() instance method. |
<Array>.contains() |
BREAKING: The polyfill for this instance method has been removed. See the <Array>.includes() instance method. |
<Array>.containsAll() |
BREAKING: This instance method has been removed. See the <Array>.includesAll() instance method. |
<Array>.containsAny() |
BREAKING: This instance method has been removed. See the <Array>.includesAny() instance method. |
<Array>.flatten() |
BREAKING: This instance method has been removed. See the <Array>.flat() instance method while providing a depth parameter of Infinity . |
Config
APISetting | Change |
---|---|
Config.macros.ifAssignError |
This setting has been deprecated and should no longer be used. See the Config.enableOptionalDebugging setting for its replacement. |
Config.passages.descriptions |
This setting has been deprecated and should no longer be used. See the Config.saves.descriptions setting for its replacement. |
Config.saves.autoload |
This setting has been deprecated and should no longer be used. The default UI now includes a Continue button, which loads the latest save. If disabling or replacing the default UI, see the Save.browser.continue() method to replicate the functionality. |
Config.saves.autosave |
This setting has been deprecated and should no longer be used. See the Config.saves.maxAutoSaves setting to set the number of available auto saves and the Config.saves.isAllowed setting to control when new auto saves are created. |
Config.saves.isAllowed |
This setting, to which you assign a function, has had the parameters provided to the assigned function changed. See its documentation entry for details. |
Config.saves.slots |
This setting has been deprecated and should no longer be used. See the Config.saves.maxSlotSaves setting for its replacement. |
Config.saves.tryDiskOnMobile |
This setting has been deprecated and should no longer be used. Saving to disk on mobile devices is now unconditionally enabled. |
Dialog
APIMethod | Change |
---|---|
Dialog.addClickHandler() |
BREAKING: This deprecated static method has been removed. |
Dialog.setup() |
This static method has been deprecated in favor of the Dialog.create() static method. |
JSON
APIMethod | Change |
---|---|
JSON.reviveWrapper() |
This static method has been deprecated in favor of the Serial.createReviver() static method. |
Macro | Change |
---|---|
<<actions>> |
This macro has been deprecated. |
<<choice>> |
This macro has been deprecated. |
<<click>> |
BREAKING: This deprecated macro has been removed. See the <<link>> macro. |
<<display>> |
BREAKING: This deprecated macro has been removed. See the <<include>> macro. |
<<forget>> |
BREAKING: This deprecated macro has been removed. See the forget() function. |
<<remember>> |
BREAKING: This deprecated macro has been removed. See the memorize() and recall() functions. |
<<setplaylist>> |
BREAKING: This deprecated macro has been removed. See the <<createplaylist>> macro. |
<<stopallaudio>> |
BREAKING: This deprecated macro has been removed. See the <<masteraudio>> macro. |
MacroContext
APIMember | Change |
---|---|
<MacroContext>.contextHas() |
This instance method has been deprecated in favor of the <MacroContext>.contextSome() instance method. |
<MacroContext>.contextSelect() |
This instance method has been deprecated in favor of the <MacroContext>.contextFind() instance method. |
<MacroContext>.contextSelectAll() |
This instance method has been deprecated in favor of the <MacroContext>.contextFilter() instance method. |
Number
APIMethod | Change |
---|---|
<Number>.clamp() |
This instance method has been deprecated. See the Math.clamp() static method. |
Passage
APIMember | Change |
---|---|
<Passage>.domId |
This instance property has been deprecated in favor of the <Passage>.id instance property. |
<Passage>.title |
This instance property has been deprecated in favor of the <Passage>.name instance property. |
<Passage>.description() |
This instance method has been deprecated. |
Save
APIMethod | Change |
---|---|
Save.get() |
BREAKING: This static method has been removed. See the Save.browser.auto.entries() and Save.browser.slot.entries() static methods for its closest replacements. |
Save.clear() |
This static method has been deprecated in favor of the Save.browser.clear() static method. |
Save.ok() |
This static method has been deprecated in favor of the Save.browser.isEnabled() static method. |
Save.autosave.delete() |
This static method has been deprecated in favor of the Save.browser.auto.delete() static method. |
Save.autosave.get() |
This static method has been deprecated in favor of the Save.browser.auto.get() static method. |
Save.autosave.has() |
This static method has been deprecated in favor of the Save.browser.auto.has() static method. |
Save.autosave.load() |
This static method has been deprecated in favor of the Save.browser.auto.load() static method. |
Save.autosave.ok() |
This static method has been deprecated in favor of the Save.browser.auto.isEnabled() static method. |
Save.autosave.save() |
This static method has been deprecated in favor of the Save.browser.auto.save() static method. |
Save.slots.length |
This static property has been deprecated in favor of the Config.saves.maxSlotSaves setting. |
Save.slots.count() |
This static method has been deprecated in favor of the Save.browser.slot.size static getter. |
Save.slots.delete() |
This static method has been deprecated in favor of the Save.browser.slot.delete() static method. |
Save.slots.get() |
This static method has been deprecated in favor of the Save.browser.slot.get() static method. |
Save.slots.has() |
This static method has been deprecated in favor of the Save.browser.slot.has() static method. |
Save.slots.isEmpty() |
This static method has been deprecated in favor of the Save.browser.slot.size static getter. |
Save.slots.load() |
This static method has been deprecated in favor of the Save.browser.slot.load() static method. |
Save.slots.ok() |
This static method has been deprecated in favor of the Save.browser.slot.isEnabled() static method. |
Save.slots.save() |
This static method has been deprecated in favor of the Save.browser.slot.save() static method. |
Save.export() |
This static method has been deprecated in favor of the Save.disk.save() static method. |
Save.import() |
This static method has been deprecated in favor of the Save.disk.load() static method. |
Save.deserialize() |
This static method has been deprecated in favor of the Save.base64.load() static method. |
Save.serialize() |
This static method has been deprecated in favor of the Save.base64.save() static method. |
Scripting
APIMethod | Change |
---|---|
Scripting.desugar() |
BREAKING: The undocumented is not to isnot operator mapping has been removed. |
Scripting.parse() |
This static method has been deprecated in favor of the Scripting.desugar() static method. |
State
APIMethod | Change |
---|---|
State.backward() |
BREAKING: This deprecated static method has been removed. |
State.display() |
BREAKING: This deprecated static method has been removed. |
State.forward() |
BREAKING: This deprecated static method has been removed. |
State.initPRNG() |
BREAKING: This deprecated static method has been removed. |
State.play() |
BREAKING: This deprecated static method has been removed. |
State.restart() |
BREAKING: This deprecated static method has been removed. |
State.show() |
BREAKING: This deprecated static method has been removed. |
Story
APIMember | Change |
---|---|
Story.domId |
This static property has been deprecated in favor of the Story.id static property. |
Story.title |
This static property has been deprecated in favor of the Story.name static property. |
StoryInterface
special passage
POSSIBLY BREAKING: The default UI's <div id="story" role="main">
container has been made a core part of the base UI, for both native and custom end user markup. As a consequence, this means that the custom markup generated by using the StoryInterface
special passage may no longer itself contain a #story
element and will be added to the core #story
container, rather than <body>
.
An example of the new hierarchy:
<body>
<div id="story" role="main">
<!-- StoryInterface elements added here -->
</div>
</body>
It is strongly recommended that you review your selectors related to the generated markup, both for DOM manipulation and CSS styling, to ensure that they're still functional. Primarily this will affect selectors that use the child combinator (>
) with a body
parent—e.g., body > …
where …
is one the elements within your custom markup.
This change was required to fix a bug related to the interaction between open dialogs and <body>
.
String
APIMethod | Change |
---|---|
<String>.readBracketedList() |
BREAKING: This deprecated instance method has been removed. |
UI
APIMethod | Change |
---|---|
UI.buildAutoload() |
This static method has been deprecated. |
UI.addClickHandler() |
BREAKING: This deprecated static method has been removed. |
UI.body() |
BREAKING: This deprecated static method has been removed. |
UI.close() |
BREAKING: This deprecated static method has been removed. |
UI.isOpen() |
BREAKING: This deprecated static method has been removed. |
UI.open() |
BREAKING: This deprecated static method has been removed. |
UI.resize() |
BREAKING: This deprecated static method has been removed. |
UI.setStoryElements() |
BREAKING: This deprecated static method has been removed. |
UI.setup() |
BREAKING: This deprecated static method has been removed. |
UI.stow() |
BREAKING: This deprecated static method has been removed. |
UI.unstow() |
BREAKING: This deprecated static method has been removed. |
UIBar
toggle and history button markup & stylesThe icons of the UI bar history control buttons have been changed from being text content in their markup to a part of their styles, as with most native SugarCube icon bearing buttons.
The styles of the UI bar toggle and history control buttons have been simplified. If you've customized the styling of any of these buttons, then it is strongly recommended that you review the ui-bar.css
file for exact details.
All changes within this version are elective changes that you may address at your leisure.
Config
APIProperty | Change |
---|---|
Config.history.maxStates |
This setting property has been updated to disallow unlimited states. |
Config.saves.onLoad |
This setting property has been deprecated in favor of the Save Events API, specifically the Save.onLoad.add static method. |
Config.saves.onSave |
This setting property has been deprecated in favor of the Save Events API, specifically the Save.onSave.add static method. |
Macro | Change |
---|---|
<<widget>> |
The special $args story variable has been deprecated in favor of the _args_ temporary variable. |
Warning: All changes within this version are breaking changes that you must address immediately.
Parser | Change |
---|---|
HTML tag | The parser has been updated to disallow use of the evaluation attribute directive on the data-setter content attribute. They were never supposed to be combined. |
All changes within this version are elective changes that you may address at your leisure.
Config
APIProperty | Change |
---|---|
Config.saves.autosave |
This setting property has been updated to accept function values and its acceptance of string values has been deprecated. String values will still be accepted for further releases of v2, however, switching to an array is recommended—e.g., the string value "autosave" would become the array ["autosave"] . See the Config.saves.autosave property for more information. |
All changes within this version are elective changes that you may address at your leisure.
Dialog
APIMethod | Change |
---|---|
Dialog.addClickHandler() |
This method has been deprecated and should no longer be used. The core of what it does is simply to wrap a call to Dialog.open() within a call to <jQuery>.ariaClick() , which can be done directly and with greater flexibility. |
Macro | Change |
---|---|
<<track>> (of: <<createplaylist>> ) |
The <<createplaylist>> macro's <<track>> child macro has had its copy keyword deprecated in favor of own . See the <<createplaylist>> macro for more information. |
<<forget>> |
The <<forget>> macro has been deprecated in favor of the forget() function. |
<<remember>> |
The <<remember>> macro has been deprecated in favor of the memorize() and recall() functions. |
Method | Change |
---|---|
<Array>.flatten() |
This method has been deprecated in favor of the <Array>.flat() method. |
State
APIMethod | Change |
---|---|
State.initPRNG() |
This method has been deprecated in favor of the State.prng.init() method. |
All changes within this version are elective changes that you may address at your leisure.
Macro | Change |
---|---|
<<cacheaudio>> |
The <<cacheaudio>> macro's original optional format specifier syntax, format:formatId;… , has been deprecated in favor of the new syntax, formatId|… .. |
All changes within this version are elective changes that you may address at your leisure.
Method | Change |
---|---|
Array.random() |
This method has been deprecated and should no longer be used. In general, look to the <Array>.random() method instead. If you need a random member from an array-like object or iterable, use the Array.from() method to convert it to an array, then use <Array>.random() —e.g., Array.from(something).random() . |
All changes within this version are elective changes that you may address at your leisure.
Macro | Change |
---|---|
<<display>> |
The <<display>> macro has been deprecated in favor of the <<include>> macro. |
All changes within this version are elective changes that you may address at your leisure.
Method | Change |
---|---|
<Array>.contains() |
The <Array>.contains() method has been deprecated in favor of the <Array>.includes() method.
|
<Array>.containsAll() |
The <Array>.containsAll() method has been deprecated in favor of the <Array>.includesAll() method.
|
<Array>.containsAny() |
The <Array>.containsAny() method has been deprecated in favor of the <Array>.includesAny() method.
|
strings
objectThe strings
API object has been replaced by the l10nStrings
object. See the Localization guide for more information.
All changes within this version are elective changes that you may address at your leisure.
Macro | Change |
---|---|
<<click>> |
The <<click>> macro has been deprecated in favor of the <<link>> macro. |
<<playlist>> |
The <<playlist>> macro has had its argument list changed, for compatibility with <<createplaylist>> . See the <<playlist>> macro for more information. |
<<setplaylist>> |
The <<setplaylist>> macro has been deprecated in favor of the <<createplaylist>> macro. |
<<stopallaudio>> |
The <<stopallaudio>> macro has been deprecated in favor of <<audio ":all" stop>> . See the <<audio>> macro for more information. |
All changes within this version are elective changes that you may address at your leisure.
config
APIThe config
API has been renamed Config
for better consistency with the other APIs.
State
APISeveral State
API methods have moved to the new Engine
API. See the Engine
API docs for more information.
Old State method |
New Engine method |
---|---|
State.backward() |
Engine.backward() |
State.display() |
Engine.display() 1 |
State.forward() |
Engine.forward() |
State.play() |
Engine.play() |
State.restart() |
Engine.restart() |
State.show() |
Engine.show() |
Engine.display()
static methods exists, it, like State.display()
before it, is deprecated. See the Engine.play()
static method for the replacement. NOTE: Their parameters differ, so read the description of Engine.play()
carefully.UI
APISeveral UI
API methods have moved to the new Dialog
API. See the Dialog
API docs for more information.
Old UI method |
New Dialog method |
---|---|
UI.addClickHandler() |
Dialog.addClickHandler() |
UI.body() |
Dialog.body() |
UI.close() |
Dialog.close() |
UI.isOpen() |
Dialog.isOpen() |
UI.open() |
Dialog.open() |
UI.setup() |
Dialog.setup() |
Warning: All changes within this version are breaking changes that you must address immediately.
The HTML & CSS have undergone significant changes. See the HTML
and CSS
docs for more information.
Passage | Change |
---|---|
MenuOptions |
The MenuOptions special passage has been removed. See the Options system section for more information. |
MenuShare |
The MenuShare special passage has been removed. See the StoryShare special passage. |
MenuStory |
The MenuStory special passage has been removed. See the StoryMenu special passage. |
config
objectThe config
object has been renamed to Config
and some of its properties have also changed. See the Config
API docs for more information.
Property | Change |
---|---|
config.altPassageDescription |
Changed the config.altPassageDescription property to Config.passages.descriptions . |
config.disableHistoryControls |
Changed the config.disableHistoryControls property to Config.history.controls . This change also inverted the meaning of the property, so take note of that. |
config.disableHistoryTracking |
Replaced the config.disableHistoryTracking property with Config.history.maxStates . The new property works differently, so take note of that. |
config.displayPassageTitles |
Changed the config.displayPassageTitles property to Config.passages.displayTitles . |
config.historyMode |
Removed the config.historyMode property. It's unnecessary since there's now only one history mode in the engine. |
config.macros.disableIfAssignmentError |
Changed the config.macros.disableIfAssignmentError property to Config.macros.ifAssignmentError . This change also inverted the meaning of the property, so take note of that. |
config.passageTransitionOut |
Changed the config.passageTransitionOut property to Config.passages.transitionOut . Additionally, it no longer accepts a boolean value, which has been replaced by the name of the animating property (necessitated by changes to browser handling of transition animations). |
config.startPassage |
Changed the config.startPassage property to Config.passages.start . |
config.updatePageElements |
Changed the config.updatePageElements property to Config.ui.updateStoryElements . |
state
)The History
API object has been renamed to State
and some of its methods have also changed. Furthermore, it is no longer instantiated into the legacy state
object—which still exists, so legacy code will continue to work. See the State
API docs for more information.
The State.display()
method—formerly state.display()
—is no longer overridable, meaning it cannot be wrapped—e.g., the "StoryRegions" 3rd-party add-ons do this. See Navigation Events or Tasks.
Calling the State.prng.init()
method—formerly History.initPRNG()
—outside of story initialization will now throw an error. It has always been required that the call happen during story initialization, the only change is the throwing of the error.
Math.random()
is no longer replaced by the integrated seedable PRNG when State.prng.init()
is called. See either the built-in functions random()
& randomFloat()
or the State.random()
method, if you need direct access to the PRNG—since it returns a call to either Math.random()
or the seedable PRNG, as appropriate.
The Macros
API object has been renamed to Macro
and several of its methods have also changed, for better consistency with the other APIs. Furthermore, it is no longer instantiated into the legacy macros
object—which still exists, so SugarCube-compatible legacy macros will continue to work. See the Macro
API docs for more information.
Macro | Change |
---|---|
<<back>> & <<return>> |
Replaced the ungainly link text syntax—<<back::linktext "…">> /<<return::linktext "…">> —with l10nStrings object properties—l10nStrings.macroBackText and l10nStrings.macroReturnText . |
<<if>> & <<elseif>> |
The <<if>> macro will now, optionally, return an error if the JavaScript = assignment operator is used (default: enabled). Configured via the Config.macros.ifAssignmentError config property. |
Options macros | The various Options macros have been removed. See the Options system section for more information. |
The entire Options system—MenuOptions
special passage, options
special variable, and associated macros—has been scrapped for numerous reasons—it was always a hack, required copious amounts of boilerplate code to be useful, etc. It is replaced by the Setting
API and settings
special variable. See the Setting
API docs for more information.
The SaveSystem
API object has been renamed to Save
and several of its methods have also changed, for better consistency with the other APIs. See the Save
API docs for more information.
The UISystem
API object has been split into two APIs Dialog
and UI
, and some of its methods have also changed. In particular, the parameter list for the Dialog.setup()
method has changed. See the Dialog
API and UI
API docs for more information.
This is a reference for localizing SugarCube's default UI text, in general, and its l10nStrings
object specifically.
Note:
If you're simply looking to download ready-to-use localizations, see the locale
directory for files in the format xx-YY.js
—where xx
is the primary code that identifies the language (e.g., en
) and YY
is the secondary code, in capital letters, that specifies the national variety (e.g., GB
or US
).
v2.0.0
: Introduced.v2.10.0
: Added l10nStrings
object. Deprecated strings
object.strings
vs. l10nStrings
Prior to SugarCube v2.10.0
, the strings localization object was named strings
. The new l10nStrings
object has a simpler, flatter, set of properties and better support for replacement strings. Unfortunately, this means that the two objects are incompatible.
To ensure backwards compatibility of existing strings
objects, if one exists within a project's scripts, the older object is mapped to the new l10nStrings
object.
The capitalization and punctuation used within the default replacement strings is deliberate, especially within the error and warning strings. You would do well to keep your translations similar when possible.
Replacement patterns have the format {NAME}
—e.g., {textIdentity}
—where NAME is the name of a property within either the l10nStrings
object or, in a few cases, an object supplied locally where the string is used—these instances will be commented.
By convention, properties starting with an underscore—e.g., _warningOutroDegraded
—are used as templates, only being included within other localized strings. Feel free to add your own if that makes localization easier—e.g., for gender, plurals, and whatnot. As an example, the default replacement strings make use of this to handle various warning intros and outros.
In use, replacement patterns are replaced recursively, so replacement strings may contain patterns whose replacements contain other patterns. Because replacement is recursive, care must be taken to ensure infinite loops are not created—the system will detect an infinite loop and throw an error.
Properties on the strings localization object (l10nStrings
) should be set within your project's JavaScript section (Twine 2: the Story JavaScript; Twine 1/Twee: a script
-tagged passage) to override the defaults.
For the template that should be used as the basis of localizations, see the locale/TEMPLATE.js
file @github.com.
// Changing the project's reported identity to 'story' (default: 'game')
l10nStrings.textIdentity = 'story';
// Changing the text of all dialog OK buttons to 'Eeyup' (default: 'OK')
l10nStrings.ok = 'Eeyup';
// Localizing the title of the Restart dialog (n.b., machine translations)
l10nStrings.restartTitle = 'Neustart'; // German (de)
l10nStrings.restartTitle = 'Reiniciar'; // Spanish (es)