Commit db47263c authored by Leon Arnott's avatar Leon Arnott
Browse files

Temp variables in custom macro calls are no longer listed in the Variables...

Temp variables in custom macro calls are no longer listed in the Variables panel in Debug Mode, due to the resulting clutter when many custom macros are used in a passage.

Also:
 * Fixed a bug where (p-not:) would not work correctly when given built-in datatypes like alnum or digit.
 * Fixed a bug where the startup error dialog box (that reports certain kinds of Javascript errors) wouldn't appear.
 * Fixed a bug where (line-style:), (enchant-in:), (link-style:) and (char-style:) did not work when used in an (enchant:) or (change:). (However, they currently still don't work when created by a via lambda given to those command macros).
 * Now, dataset data should be displayed correctly in the Variables panel in Debug Mode.
 * Fixed various documentation lines, and added an "Example Stories" section to the Introduction.
parent 2044cf5b7eb3
......@@ -16,6 +16,14 @@ Documentation is at http://twine2.neocities.org/. See below for compilation inst
* Fixed a bug where `(icon-restart:)` didn't restart the story when clicked.
* Fixed a bug where, upon supplying a label string to `(meter:)`, any updates to the meter would cause that label to gain any styles, such as `(border:)` borders, that had been given to the meter itself.
* Fixed a bug where `(input-box:)` crashed upon the first inputted character if you didn't supply the optional bound variable.
* Fixed a bug where `(p-not:)` would not work correctly when given built-in datatypes like `alnum` or `digit`.
* Fixed a bug where the startup error dialog box (that reports certain kinds of Javascript errors) wouldn't appear.
* Fixed a bug where `(line-style:)`, `(enchant-in:)`, `(link-style:)` and `(char-style:)` did not work when used in an `(enchant:)` or `(change:)`. (However, they currently still don't work when created by a `via` lambda given to those command macros).
* Now, dataset data should be displayed correctly in the Variables panel in Debug Mode.
#### Alterations
* Now, temp variables in custom macro calls are no longer listed in the Variables panel in Debug Mode, due to the resulting clutter when many custom macros are used in a passage.
### 3.2.1 changes (Jan 18, 2021):
......
......@@ -181,7 +181,7 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
let localTempVariables = new Set();
const Variables = Panel.create({
className: "variables", tabName: "Variable",
rowAdd({name, path, value, tempScope, type}) {
rowAdd({name, dataset, path, value, tempScope, type}) {
/*
The debug name used defers to the TwineScript_DebugName if it exists,
and falls back to the objectName if not. Note that the TwineScript_DebugName can contain HTML structures
......@@ -198,6 +198,12 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
if (path.length) {
trail = path.reduce((a,e) => a + e + "'s ", '');
}
/*
Dataset entries, which use Numbers as names instead of user-facing strings, should be converted to "???".
*/
if (dataset) {
name = "???";
}
/*
Typed variables should have their type restriction listed.
*/
......@@ -245,7 +251,7 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
);
},
rowCheck({name, path, tempScope}, row) {
return row.attr('data-name') === name && row.attr('data-path') === (path+'') && row.attr('data-scope') === tempScope;
return row.attr('data-name') === name && row.attr('data-path') === (path+'');
},
columnHead() {
return `<tr class="panel-head"><th>Type</th><th>Name</th><th>Scope</th><th>Value</th></tr>`;
......@@ -288,8 +294,8 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
Sets don't have keys. So, using "???" for every entry is what we're forced to do
to keep this display consistent with the others.
*/
[...value].forEach((elem) =>
recursiveUpdateVariables({name:"???", path, value:elem, tempScope})
[...value].forEach((elem,i) =>
recursiveUpdateVariables({name: i, dataset:true, path, value:elem, tempScope})
);
}
}
......@@ -328,7 +334,12 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
Since temp variables' variable stores are tied to sections and can't be easily accessed
from here, add their variable rows on each set() rather than getting updateVariables() to do it.
*/
if (obj !== State.variables && obj.TwineScript_VariableStoreName) {
if (obj !== State.variables && obj.TwineScript_VariableStoreName &&
/*
Custom macro variables are prohibited from appearing in the Variables panel.
*/
!obj.TwineScript_VariableStoreName.match(/#\d+$/)
) {
const tempScope = obj.TwineScript_VariableStoreName;
const type = obj.TwineScript_TypeDefs && obj.TwineScript_TypeDefs[name];
/*
......@@ -406,7 +417,13 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
.data('enchantment',enchantment)
.append(
"<td><span class='enchantment-name'>" + toSource(scope)
+ (localHook ? "</span><span class=enchantment-local>" + (localHook.attr('name') ? "?" + localHook.attr('name') : "an unnamed hook") : "") + "</span>"
+ (localHook ? "</span><span class=enchantment-local>"
/*
localHooks can be jQuerys (for plain attachment) or HookSets (for use in (enchant:)).
*/
+ (typeof localHook.TwineScript_ToSource === "function" ? localHook.TwineScript_ToSource() :
localHook.attr('name') ? "?" + localHook.attr('name') : "an unnamed hook") : "")
+ "</span>"
+ "</td><td class='enchantment-value'>"
+ val + "</td>"
+ (changer ? "<td class='panel-row-buttons'>"
......
......@@ -23,7 +23,7 @@ require.config({
require(['jquery', 'debugmode/mode', 'renderer', 'state', 'section', 'engine', 'passages', 'utils', 'utils/renderutils', 'internaltypes/varscope', 'internaltypes/twineerror', 'macros',
'macrolib/values', 'macrolib/commands', 'macrolib/datastructures', 'macrolib/stylechangers', 'macrolib/enchantments', 'macrolib/metadata', 'macrolib/patterns',
'macrolib/links', 'macrolib/custommacros', 'repl'],
($, DebugMode, Renderer, State, Section, Engine, Passages, Utils, VarScope, {dialog}, TwineError) => {
($, DebugMode, Renderer, State, Section, Engine, Passages, Utils, {dialog}, VarScope, TwineError) => {
/*
Harlowe, the default story format for Twine 2.
......
......@@ -89,9 +89,9 @@ define(['jquery', 'utils', 'renderer', 'datatypes/hookset'], ($, {assertOnlyHas,
// Used only by (link:), (live:) and (event:).
data: null,
// {Object} [functions] Some arbitrary functions to execute, passing the target element, after rendering (and no other time).
// Used only by (enchant-in:).
functions: null,
// {Object} [innerEnchantments] Functions which add enchantments that are specific to the hook.
// Because of a circular dependency, they call addEnchantment themselves. Used only by (enchant-in:) and its ilk.
innerEnchantments: null,
// {Object} [section] A Section that 'owns' this ChangeDescriptor.
section: null,
......@@ -108,7 +108,7 @@ define(['jquery', 'utils', 'renderer', 'datatypes/hookset'], ($, {assertOnlyHas,
return [
"source", "appendSource", "enabled", "verbatim", "target", "append", "newTargets",
"transition", "transitionTime", "transitionDeferred", "transitionDelay",
"transitionSkip", "transitionOrigin", "functions", "enablers",
"transitionSkip", "transitionOrigin", "innerEnchantments", "enablers",
]
.filter(e => this.hasOwnProperty(e))
.concat([
......@@ -271,7 +271,7 @@ define(['jquery', 'utils', 'renderer', 'datatypes/hookset'], ($, {assertOnlyHas,
*/
render() {
const
{source, transition, transitionTime, transitionDeferred, enabled, enablers, data, section, newTargets, functions, appendSource} = this;
{source, transition, transitionTime, transitionDeferred, enabled, enablers, data, section, newTargets, innerEnchantments, appendSource} = this;
let
{target, target:oldTarget, append} = this;
......@@ -567,11 +567,10 @@ define(['jquery', 'utils', 'renderer', 'datatypes/hookset'], ($, {assertOnlyHas,
}
/*
After everything else is done, any especially arbitrary functions that need to be performed
(such as creating an enchantment for (enchant-in:)) are done here.
After everything else is done, inner (enchant-in:) enchantments are created here.
*/
if (functions) {
functions.forEach(fn => fn(target));
if (innerEnchantments) {
innerEnchantments.map(fn => fn(target)).forEach(ench => section.addEnchantment(ench));
}
return dom;
......
......@@ -51,13 +51,13 @@ define(['jquery', 'utils', 'internaltypes/changedescriptor', 'datatypes/changerc
*/
scope.forEach(section, (e, i) => {
/*
The localHook is a restriction created by (enchant-in:), limiting the given scope even further.
While it should probably be changed into an extension of the HookSet class, it currently allows
for even unnamed hooks (such as those that a changer would be attached to) to be the restricted area,
which would be difficult with even current (Sep 2020) user-facing HookSet syntax.
The localHook is a restriction created by (enchant-in:) or its ilk, limiting the given scope even further.
It is either a HookSet (when used with (enchant:)) or a jQuery (when attached to a hook)
*/
if (localHook && !localHook.has(e[0]).length) {
return;
if (localHook) {
if (!(localHook.jquery ? localHook : localHook.hooks(section)).has(e[0]).length) {
return;
}
}
/*
Lambdas are given to enchantments exclusively through (enchant:). They override any
......
......@@ -614,7 +614,7 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
"Temp variables only exist inside the same passage and hook in which they're (set:).");
}
if (Array.isArray(obj) && typeof prop === "number") {
return TwineError.create("property", "This array of " + (obj.length+1) + " elements doesn't have a "
return TwineError.create("property", "This array of " + (obj.length) + " elements doesn't have a "
+ propertyDebugName(originalProp)
+ " element.",
obj.length ? "It contains: " + andList(obj.map(objectName)) + '.' : "The array is empty.");
......
......@@ -521,7 +521,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
A convenient combination of (verbatim-print:) and (source:), this prints out the Harlowe source code of any value given to it.
Example usage:
`(v6m-source: (open-storylets:)'s 1st)` prints the source of the first datamap in the array generated by (open-storylets:).
`(v6m-source: (open-storylets: )'s 1st)` prints the source of the first datamap in the array generated by (open-storylets:).
Rationale:
This macro provides a quick way for you to display the source code of a Harlowe value. Normally, you can't easily print the string
......
......@@ -41,7 +41,8 @@ define([
use `last` to refer to the last position, `2ndlast` to refer to the second-last, and so forth. Arrays also have
a `length` number: `$array's length` tells you how many values are in it. If you can't determine the exact position
to remove an item from (because it's dependent on a variable), you can use an expression, in brackers,
after it: `$array's ($pos - 3)`.
after it: `$array's ($pos - 3)`. This syntax can be chained: if an array is inside another data structure (for instance, by
`(set: $array to (a:(a:1,2),(a:2,3)))`) then you can write `$array's 1st's 1st` to access the 1 stored in the inner array.
**Note:** While you can normally display the contents of variables by simply placing their names directly in passage prose,
such as `$votes`, you have to use another macro, such as (print:), to display the contents of arrays, such as `(print: $votes's 1st)`.
......@@ -1272,10 +1273,11 @@ define([
Datamaps consist of several string *name*s, each of which maps to a specific *value*. `$animals's frog` and `frog of $animals`
refers to the value associated with the name 'frog'. You can add new names or change existing values by using (set:) -
`(set: $animals's wolf to "howl")`.
You can express the name as a bare word if it doesn't have a space or other punctuation in it - `$animals's frog` is OK, but
`(set: $animals's wolf to "howl")`. You can express the name as a bare word if it doesn't have a space or other punctuation in it - `$animals's frog` is OK, but
`$animals's komodo dragon` is not. In that case, you'll need to always supply it as a string - `$animals's "komodo dragon"`.
This syntax can be chained: if a datamap is inside another data structure (for instance, by
`(set: $arr to (a:(dm:'name', 'silver ring', 'resaleValue', 250),(dm:'name', 'a button', 'resaleValue', 0)))`)
then you can write `$arr's 1st's resaleValue` to access the 250 in the first datamap.
**Note:** While you can normally display the contents of variables by simply placing their names directly in passage prose,
such as `$sandwich`, you have to use another macro, such as (print:), to display the contents of datamaps, such as `(print: $sandwich's bread)`.
......
"use strict";
define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages', 'macros', 'datatypes/hookset', 'datatypes/changercommand', 'datatypes/lambda', 'internaltypes/enchantment', 'internaltypes/twineerror'],
($, Utils, {is}, Engine, State, Passages, Macros, HookSet, ChangerCommand, Lambda, Enchantment, TwineError) => {
define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages', 'macros', 'datatypes/hookset', 'datatypes/changercommand', 'datatypes/lambda', 'internaltypes/changedescriptor', 'internaltypes/enchantment', 'internaltypes/twineerror'],
($, Utils, {is}, Engine, State, Passages, Macros, HookSet, ChangerCommand, Lambda, ChangeDescriptor, Enchantment, TwineError) => {
const {either,rest,optional} = Macros.TypeSignature;
/*
......@@ -143,8 +143,29 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
}
},
(section, scope, changer) => {
const enchantment = Enchantment.create({
scope: HookSet.from(scope),
scope = HookSet.from(scope);
const enchantments = [];
/*
If the changer (and currently it must be a changer) contains innerEnchantments, then those must be added to this enchantment
now (because ChangerCommand.update() certainly won't touch them).
*/
if (ChangerCommand.isPrototypeOf(changer)) {
/*
These two lines are similar to those used by ChangerCommand.summary().
*/
const desc = ChangeDescriptor.create({section});
changer.run(desc);
if ((desc.innerEnchantments || []).length > 0) {
/*
innerEnchantments contains functions that produce Enchantments. These are run
giving this outer (enchant:)'s scope as the localHook to which the Enchantment's own scope is constrained.
*/
const c = desc.innerEnchantments.map(e => e(scope));
enchantments.push(...c);
}
}
enchantments.push(Enchantment.create({
scope,
/*
Because the changer provided to this macro could actually be a "via" lambda that produces changers,
Enchantment has both "lambda" and "changer" properties to distinguish them.
......@@ -152,19 +173,21 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
*/
[ChangerCommand.isPrototypeOf(changer) ? "changer" : "lambda"]: changer,
section,
}));
enchantments.forEach(e => {
if (name === "enchant") {
section.addEnchantment(e);
/*
When this is run in a normal section renderInto() flow, section.updateEnchantments() will
be run automatically after this has been executed. However, if this was run as a result of an unblocked flow
(via (dialog:) etc.) then it still needs to be run manually here.
*/
section.updateEnchantments();
}
else {
e.enchantScope();
}
});
if (name === "enchant") {
section.addEnchantment(enchantment);
/*
When this is run in a normal section renderInto() flow, section.updateEnchantments() will
be run automatically after this has been executed. However, if this was run as a result of an unblocked flow
(via (dialog:) etc.) then it still needs to be run manually here.
*/
section.updateEnchantments();
}
else {
enchantment.enchantScope();
}
return "";
},
[either(HookSet,String), either(ChangerCommand, Lambda.TypeSignature('via'))],
......@@ -209,6 +232,8 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
This enchantment will be listed in the "Enchantments" tab of the Debug Mode panel when it's active, alongside enchantments created by (enchant:).
Due to Harlowe engine limitations, this currently does NOT work when created by a lambda given to `(enchant:)` or `(change:)`, such as in `(enchant: ?passage, via (enchant-in:?frogs,(bg:(hsl:pos*30,0.5,1))))`.
See also:
(enchant:), (change:), (link-style:)
......@@ -225,17 +250,15 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
},
(desc, scope, changer) => {
/*
The 'functions' property of ChangeDescriptors only exists for this macro (currently) to solve a circular dependency
where Enchantment <- ChangeDescriptor <- Enchantment. This function creates an enchantment
The 'innerEnchantments' property of ChangeDescriptors is an array of functions, in order to solves a circular dependency
where Enchantment <- ChangeDescriptor <- Enchantment.
*/
desc.functions = (desc.functions || []).concat(localHook => {
desc.section.addEnchantment(Enchantment.create({
scope: HookSet.from(scope),
localHook,
[ChangerCommand.isPrototypeOf(changer) ? "changer" : "lambda"]: changer,
section: desc.section,
}));
});
desc.innerEnchantments = (desc.innerEnchantments || []).concat(localHook => Enchantment.create({
scope: HookSet.from(scope),
localHook,
[ChangerCommand.isPrototypeOf(changer) ? "changer" : "lambda"]: changer,
section: desc.section,
}));
return desc;
},
[either(HookSet,String), either(ChangerCommand, Lambda.TypeSignature('via'))]
......@@ -267,6 +290,8 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
This will also apply the style changer to (click:) links inside the hook.
Due to Harlowe engine limitations, this currently does NOT work when created by a lambda given to `(enchant:)` or `(change:)`, such as in `(enchant: ?passage, via (line-style,(bg:(hsl:pos*30,0.5,1))))`.
See also:
(enchant-in:), (hover-style:), (line-style:), (char-style:)
......@@ -313,6 +338,8 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
This creates a hook-specific enchantment, similar to (enchant-in:), It will be listed under the "Enchantments" tab of the Debug Mode panel.
Due to Harlowe engine limitations, this currently does NOT work when created by a lambda given to `(enchant:)` or `(change:)`, such as in `(enchant: ?passage, via (line-style,(bg:(hsl:pos*30,0.5,1))))`.
See also:
(enchant-in:), (hover-style:), (link-style:), (char-style:)
......@@ -346,6 +373,8 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
**Warning:** using (char-style:) to enchant very large amounts of text at once will likely cause excessive CPU load for the reader, making their browser unresponsive.
Due to Harlowe engine limitations, this currently does NOT work when created by a lambda given to `(enchant:)` or `(change:)`, such as in `(enchant: ?passage, via (char-style,(bg:(hsl:pos*30,0.5,1))))`.
See also:
(enchant-in:), (hover-style:), (link-style:), (line-style:)
......@@ -367,16 +396,14 @@ define(['jquery', 'utils', 'utils/operationutils', 'engine', 'state', 'passages'
},
(desc, changer) => {
/*
The 'functions' property of ChangeDescriptors only exists for this macro (currently) to solve a circular dependency
where Enchantment <- ChangeDescriptor <- Enchantment. This function creates an enchantment
The 'innerEnchantments' property of ChangeDescriptors solves a circular dependency
where Enchantment <- ChangeDescriptor <- Enchantment.
*/
desc.functions = (desc.functions || []).concat(localHook => {
desc.section.addEnchantment(Enchantment.create({
scope, localHook,
[ChangerCommand.isPrototypeOf(changer) ? "changer" : "lambda"]: changer,
section: desc.section,
}));
});
desc.innerEnchantments = (desc.innerEnchantments || []).concat(localHook => Enchantment.create({
scope, localHook,
[ChangerCommand.isPrototypeOf(changer) ? "changer" : "lambda"]: changer,
section: desc.section,
}));
return desc;
},
[either(ChangerCommand, Lambda.TypeSignature('via'))]
......
......@@ -451,7 +451,7 @@ define(['jquery', 'macros', 'utils', 'utils/operationutils', 'datatypes/datatype
}
return createPattern({
name: "p-not", fullArgs, canContainTypedVars: false,
makeRegExpString: subargs => "[^" + subargs.join('') + "]"
makeRegExpString: subargs => "[^" + subargs.map(e => (e.startsWith("[") && e.endsWith("]")) ? e.slice(1,-1) : e).join('') + "]"
});
},
PatternSignature)
......
......@@ -89,7 +89,6 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
can now be reinstated, erasing the hover style.
*/
mouseoutStyle = elem.data('mouseoutStyle');
elem.attr('style', mouseoutStyle)
.removeData('mouseoutStyle')
.attr('hover',false);
......@@ -1085,7 +1084,9 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
| "inset" | <span style="border: 2px inset black;margin:2px;display:inline-block">Example text</span>
| "outset" | <span style="border: 2px outset black;margin:2px;display:inline-block">Example text</span>
The "none" type can be used to remove a border that another changer may have included.
The "none" type can be used to remove a border that another changer may have included. NOTE: As of Harlowe 3.2.2,
this can only be used to remove borders from combined changers, such as by `(set: $changer to it + (b4r:"none"))`,
and can't be used to remove borders from already-changed hooks or other structures.
The default size of the border, with no other CSS changes to any elements, is 2px (2 pixels),
unless a change is applied using (border-size:).
......@@ -1831,7 +1832,9 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
| "buoy" | <t-s style="display:inline-block;animation:buoy 5s linear 0s infinite"></t-s> | "fade-in-out", "blink", "shudder", "sway", "fidget"
| "fidget" | <t-s style="display:inline-block;animation:fidget 60s step-end 0s infinite"></t-s> | "fade-in-out", "blink", "rumble", "shudder", "sway", "buoy"
You can use the "none" style to remove an existing style from a combined changer.
You can use the "none" style to remove an existing style from a combined changer. NOTE: As of Harlowe 3.2.2,
this can only be used to remove styles from combined changers, such as by `(set: $changer to it + (text-style:"none"))`,
and can't be used to remove styles from already-changed hooks or other structures.
Due to browser limitations, combining many of these changers won't work exactly as intended – `(text-style: "underline", "strike")`, for instance,
will cause only the latter of the two to be applied, in this case "strike". These incompatibilities are listed in the table above.
......@@ -2068,8 +2071,9 @@ define(['jquery','macros', 'utils', 'utils/renderutils', 'datatypes/colour', 'da
Note that in versions of Harlowe prior to 3.2.0, this could be combined with (link:), (link-repeat:), or (link-reveal:)
to apply changers to the link, except for (text-colour:). This has since been changed, and now, when combined with (link:)
changers, (hover-style:) will only apply to the revealed hook. Note that `(link-goto:)` and passage links aren't changers,
so (hover-style:) can be attached to them, as expected.
changers, (hover-style:) will only apply to the revealed hook. (The intended way to style the link in that case is to provide (hover-style:) as
the optional second value to a link changer, such as by `(link-rerun:"Retry", (hover-style:(color:red)))`.)
Note that `(link-goto:)` and passage links aren't changers, so (hover-style:) can be attached to them, as expected.
See also:
(link-style:), (line-style:), (char-style:)
......
......@@ -14,6 +14,6 @@ I'd appreciate it if you could adhere to the following guidelines when reporting
* If your issue is a bug, please set the "Label" to "Bug".
* You *may* suggest feature requests, but please set the issue's "Label" to "Proposal".
### The issues page has recently moved!
### The issues page moved awhile ago!
As of July 2020, Harlowe's issues page and hosting is now at [foss.heptapod.net](https://foss.heptapod.net). Big thanks to [Octobus SAS](https://octobus.net/) for providing this free open-source software hosting service, and [Clever Cloud SAS](https://www.clever-cloud.com/) for that service's hosting.
\ No newline at end of file
Introduction 5: Example stories
Here are a few example stories, written by me, Leon, and designed to be downloaded and opened in the Twine editor for reference and experimentation. These stories' prose and Harlowe code (though, of course, not the Harlowe engine itself) are entirely public domain - use their contents for your own projects as you wish.
### [Quack of Duckness](./Quack_of_Duckness.html) <a href="./Quack_of_Duckness.html" download="Quack of Duckness.html">[download]</a>.
This is a parody of the [example story](https://klembot.github.io/chapbook/examples/cloak-of-darkness.html) for the Chapbook story format, "Cloak of Darkness". This demonstrates several basic Harlowe features:
* Using variables to track the player's actions.
* Using (if:) with the "visits" keyword to display text only once.
* Using (click-append:) to add a link in the middle of a paragraph.
* Using (transition:) to alter the transition of a hook, both as an attached changer and as an additional value given to (click-append:).
* Using a boolean variable to enable a hook, similar to (if:).
* Using escaped line break syntax to control whitespace in the story.
* Using "footer" tagged passages to place text beneath every passage in the story.
* Using (enchant:) with (t8n-depart:) and (t8n-arrive:) inside a "footer" tagged passage to affect the transition of every passage link in the story.
* Using (append:) with the ?sidebar hook name to add icons to the sidebar.
Additionally, it contains an extra passage with a "character creator" board, which allows players to spend statistic points to increase character attributes, in the manner of an RPG. This board makes use of these features:
* Using named hooks and (rerun:) to alter a statistical readout whenever the player modifies one of the statistics.
* Using temp variables to store complicated commands such as (link:) that need to be used multiple times in the passage.
### [The Basics of TBC](./The_Basics_of_TBC.html) <a href="./The_Basics_of_TBC.html" download="The Basics of TBC.html">[download]</a>.
This demonstrates how one would implement a very simple 1-vs-1 turn-based-combat (TBC) engine in Harlowe, in the manner of an RPG. It provides examples of the following features:
* Using (click:) with ?page to only advance the story when the mouse is clicked.
* Using custom macros to reduce the amount of overall code that needs to be written.
* Using temp variables to store calculated values that are used further down the passage.
* Using datamaps (created via (dm:)) to greatly simplify the process of defining unique turn-based-combat opponents, as well as reducing the amount of variables that need to be created to store data relating to the battle's state.
* Using arrays (created via (a:)) to store sequential data, and using (move:) and (rotated:) to change the first value of those arrays.
### [Styling with Enchantments](./Styling_with_Enchantments.html) <a href="./Styling_with_Enchantments.html" download="Styling with Enchantments.html">[download]</a>.
This demonstrates a number of ways you can style your stories without needing to use CSS stylesheets. All of the styles in this story are coded in separate passages, and their code is free to use in your stories. They provide examples of the following features:
* Using (enchant:) with ?page, ?passage and ?link to alter the visual presentation of various parts of the page.
* Using (hover-style:) to change how links appear when the mouse hovers over them.
* Using (background:) with (gradient:) and (stripes:).
* Using (hide:) and (replace:) with ?sidebar to alter the sidebar.
Enjoy the examples!
......@@ -6,7 +6,7 @@
"license": "Zlib",
"repository": {
"type": "hg",
"url": "https://bitbucket.org/_L_/harlowe"
"url": "https://foss.heptapod.net/games/harlowe/"
},
"contributors": [
{
......
......@@ -456,7 +456,7 @@ paths.forEach(function(path) {
let defs;
// .md files should be treated as entire single definitions
if (path === "README.md") {
defs = file.match(/### \d\.\d\.\d[^]*?\n(?=###[^#])/g).slice(0,2);
defs = file.match(/### \d\.\d\.\d[^]*?\n(?=###[^#])/g).slice(0,3);
} else if (path.endsWith('.md')) {
defs = [file];
} else {
......
......@@ -274,3 +274,10 @@ tw-colour {
width: 1em;
height: 1em;
}
/*
Empty enchantments shouldn't be able to occur, so this exists merely as a safeguard.
*/
tw-enchantment:empty {
display:none;
}
\ No newline at end of file
......@@ -272,6 +272,14 @@ describe("enchantment macros", function () {
it("doesn't create <tw-pseudo-hook>s", function() {
expect(runPassage('(link-style: (color:#800000))[[qux<-bar]]').find('tw-pseudo-hook').length).toBe(0);
});
it("works with (enchant:)", function() {
runPassage('(enchant:?passage,(link-style: (color:#800000)))[[test<-bar]]');
expect($('tw-link').css('color')).toMatch(/(?:#800000|rgb\(\s*128,\s*0,\s*0\s*\))/);
});
it("does not work with (enchant:) when used in a lambda", function() {
runPassage('(enchant:?passage, via (link-style: (color:#800000)))[[test<-bar]]');
expect($('tw-link').css('color')).not.toMatch(/(?:#800000|rgb\(\s*128,\s*0,\s*0\s*\))/);
});
});
describe("(line-style:)", function() {
it("accepts a changer command or a 'via' lambda", function() {
......@@ -310,6 +318,14 @@ describe("enchantment macros", function () {
it("doesn't create <tw-pseudo-hook>s", function() {
expect(runPassage('(line-style: (text-colour:#333))[=foo\nbar\n').find('tw-pseudo-hook').length).toBe(0);
});
it("works with (enchant:)", function() {
runPassage('(enchant:?passage,(line-style: (color:#800000)))\ngooball');
expect($('tw-passage tw-enchantment').css('color')).toMatch(/(?:#800000|rgb\(\s*128,\s*0,\s*0\s*\))/);
});
it("does not work with (enchant:) when used in a lambda", function() {
runPassage('(enchant:?passage, via (line-style: (color:#800000)))\ngooball');
expect($('tw-passage tw-enchantment').css('color')).not.toMatch(/(?:#800000|rgb\(\s*128,\s*0,\s*0\s*\))/);
});
});
describe("(char-style:)", function() {
it("accepts a changer command or a 'via' lambda", function() {
......@@ -340,5 +356,13 @@ describe("enchantment macros", function () {
it("doesn't create <tw-pseudo-hook>s", function() {
expect(runPassage('(char-style:(background:green))[=foo\n[bar]<a|\n').find('tw-pseudo-hook').length).toBe(0);
});
it("works with (enchant:)", function() {
runPassage('(enchant:?passage,(char-style: (color:#800000)))\ng');
expect($('tw-passage tw-enchantment').css('color')).toMatch(/(?:#800000|rgb\(\s*128,\s*0,\s*0\s*\))/);
});
it("does not work with (enchant:) when used in a lambda", function() {
runPassage('(enchant:?passage, via (char-style: (color:#800000)))\ng');
expect($('tw-passage tw-enchantment').css('color')).not.toMatch(/(?:#800000|rgb\(\s*128,\s*0,\s*0\s*\))/);
});
});
});
......@@ -442,8 +442,11 @@ describe("patterns", function() {
});
describe("when given a string pattern", function() {
it("splits the data string into an array of substrings between matches", function() {
expect("(print:(split: (p:'#'), 'abe#ced#bee') is (a:'abe','ced','bee'))").markupToPrint('true');
expect('(print:(split:(p-many:(p-either:".",whitespace)),"AB CD FG.H"))').markupToPrint("AB,CD,FG,H");
expect('(print:(split:(p-ins:"e"),"ABECDEFGEH"))').markupToPrint("AB,CD,FG,H");
expect("(print:(split:(p-not:...'abcde'), 'abe4ced2bee') is (a:'abe','ced','bee'))").markupToPrint('true');
expect("(print:(split:(p-not:alnum), 'abe+ced+bee') is (a:'abe','ced','bee'))").markupToPrint('true');
});
it("returns nothing if the pattern covers the whole string", function() {
expect('(print:(split:(p:"AB", alnum, "DE"),"ABCDE"))').markupToPrint("");
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment