Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrade bondage to the yarn 2 spec - closes 6451 #6466

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Extensions/DialogueTree/JsExtension.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@

/** @type {ExtensionModule} */
module.exports = {
// todo do we need some special type of importing when dealing with umd modules?
// registerInstanceRenderers: function (objectsRenderingService) {
// const bondage = objectsRenderingService.requireModule(
// __dirname,
// 'bondage.js/dist/bondage.min.js'
// );
// console.log({bondage})
// }
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
Expand Down Expand Up @@ -50,8 +58,8 @@ module.exports = {
false
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/DialogueTree/dialoguetools.js')
.addIncludeFile('Extensions/DialogueTree/bondage.js/dist/bondage.min.js')
.setIncludeFile('Extensions/DialogueTree/bondage.js/dist/bondage.js')
.addIncludeFile('Extensions/DialogueTree/dialoguetools.js')
.setFunctionName('gdjs.dialogueTree.loadFromSceneVariable');

extension
Expand Down
318 changes: 272 additions & 46 deletions Extensions/DialogueTree/bondage.js/README.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,293 @@
# bondage.js [![Build Status](https://travis-ci.org/jhayley/bondage.js.svg?branch=master)](https://travis-ci.org/jhayley/bondage.js)
This project is a runner for [Yarn](https://yarnspinner.dev/) dialogues, attempting compliance with the 2.0 language specification.

API improvements and additional features are present in [YarnBound](https://github.com/mnbroatch/yarn-bound), which uses this package under the hood.

# Known Deviations from Yarn 2.0 spec

- Reading from a .yarn file is left to the user; dialogues should be supplied to bondage.js as a text string or array of node objects.
- Some minutia about what unicode characters define a string has not been considered.

There are features in the Yarn docs that are not present in the [Yarn language spec](https://github.com/YarnSpinnerTool/YarnSpinner/blob/9275277f50a6acbe8438b29596acc8527cf5581a/Documentation/Yarn-Spec.md). Known examples are:
- `Character: some text` annotation
- `[b]Markup[/b]`

These exist in [YarnBound](https://github.com/mnbroatch/yarn-bound) but not here.

[Yarn](https://github.com/InfiniteAmmoInc/Yarn) parser for Javascript, in the same vein as [YarnSpinner](https://github.com/thesecretlab/YarnSpinner).

# Usage

#### As a Web Tool
Install with `npm i -S bondage` or grab `bondage.js` from the `/dist/` folder.

For information on how to write Yarn, visit the [official documentation](https://docs.yarnspinner.dev/).

The examples below illustrate how `bondage.js` in particular works:


### Basic Dialogue

```javascript
import bondage from 'bondage';
// or node:
// const bondage = require('bondage')
// or in a script tag:
// <script src="path-to-file/bondage.min.js"></script>

// bondage.js strips empty lines, but make sure lines have
// no leading whitespace (besides indentation)!
const dialogue = `
# someFiletag
title: StartingNode
someTag: someTag
---
This is a line of text.#someHashtag
This is another line of text.
===
`

const runner = new bondage.Runner()
runner.load(dialogue)
const generator = runner.run('StartingNode')
let node = generator.next().value
console.log('node', node)
```

When we log out `node` above, we will see this object structure:

```javascript
{
"text": "This is a line of text.",
"hashtags": ['someHashtag'],
"metadata": {
"title": "StartingNode",
"someTag": "someTag",
"filetags": [
"someFiletag"
]
}
}
```

Notice that hashtags at the end of a line go in a `hashtags` array.

to continue, we call

```javascript
node = generator.next().value
```

again, and if we log the new node, we see:

```javascript
{
"text": "This is another line of text.",
"hashtags": [],
"metadata": {
"title": "StartingNode",
"someTag": "someTag",
"filetags": [
"someFiletag"
]
}
}
```

If we had jumped, we would see the new node's title and header tags under the `metadata` property (along with the same fileTags).


### Options

Given this dialogue:

```
# someFiletag
title: StartingNode
someTag: someTag
---
What color do you like?
-> Red
You picked Red!
-> Blue
You picked Blue!
===
```

We can start the dialogue runner like above.

```javascript
const runner = new bondage.Runner()
runner.load(dialogue)
const generator = runner.run('StartingNode')
let node = generator.next().value
```

which will give us a text result like the last example. However, the next node we get from calling `generator.next().value` will be:

```javascript
{
"options": [
{
"text": "Red",
"isAvailable": true,
"hashtags": []
},
{
"text": "Blue",
"isAvailable": true,
"hashtags": []
}
],
"metadata": {
"title": "StartingNode",
"someTag": "someTag",
"filetags": [
"someFiletag"
]
}
}
```

In order to continue the dialogue, you will need to call

```javascript
node.select(0);
node = generator.next().value
```

in order to move to the line with text, "You picked Red!"

To run through your yarn files in your browser, go to <http://hayley.zone/bondage.js>, paste your yarn data in the field, then hit "compile".
But how will your view layer know whether you're looking at a text result or an options result? Use `instanceof`:

#### As a Command Line Tool
Installation: `npm install -g bondage`
`node instanceof bondage.TextResult`

Now you can use the `bondage` command to run through Yarn files from the command line. You can load one or multiple files at a time. If you load multiple files and a two nodes are encountered with the same name, the node will be overwritten.
`node instanceof bondage.OptionsResult`

**Examples**
`node instanceof bondage.CommandResult`

* Running a single file from the default start node (named "Start"): `bondage run yarnfile.json`
* Running a single file from the specified node name: `bondage run -s StartNode yarnfile.json`
* Running multiple files from the specified node name: `bondage run -s StartNode yarnfile1.json yarnfile2.json ...`
* See the compiled ast: `bondage compile --ast yarnfile.json`
* See the tokenized input: `bondage compile --tokens yarnfile.json`
Speaking of CommandResult...

#### As a Library

**Web**
# Commands

Include [dist/bondage.min.js](https://github.com/jhayley/bondage.js/blob/master/dist/bondage.min.js) somewhere in your html, and the `bondage` variable will be added to the global scope. You can then access everything in the example below (such as `bondage.Runner`) through that variable.
The third and last result type you need to know about is CommandResult. Given this dialogue:

**Node**
```
# someFiletag
title: StartingNode
someTag: someTag
---
Sending a command...
<<someCommand with spaces>>
===
```

You will see a "Sending a command..." TextResult, but the next node will look like this:

Installation: `npm install bondage`

```javascript
const fs = require('fs');
const bondage = require('bondage');

const runner = new bondage.Runner();
const yarnData = JSON.parse(fs.readFileSync('yarnFile.json'));

runner.load(yarnData);

// Loop over the dialogue from the node titled 'Start'
for (const result of runner.run('Start')) {
// Do something else with the result
if (result instanceof bondage.TextResult) {
console.log(result.text);
} else if (result instanceof bondage.OptionsResult) {
// This works for both links between nodes and shortcut options
console.log(result.options);

// Select based on the option's index in the array (if you don't select an option, the dialog will continue past them)
result.select(1);
} else if (result instanceof bondage.CommandResult) {
// If the text was inside <<here>>, it will get returned as a CommandResult string, which you can use in any way you want
console.log(result.text);
{
"name": "someCommand with spaces",
"hashtags": [],
"metadata": {
"title": "StartingNode",
"someTag": "someTag",
"filetags": [
"someFiletag"
]
}
}
```

Your program can do what it wants with that, then call `generator.next().value` to get the next node, as usual.


### Custom Variable Storage

Bondage keeps track of variables internally. Optionally, you can supply your own variableStorage. variableStorage is an object with get() and set() methods defined.

```javascript
const customStorage = new Map()
customStorage.set('hello', 1)

const runner = new bondage.Runner()
runner.setVariableStorage(customStorage)
runner.load(dialogue)
```

**Call setVariableStorage BEFORE loading a dialogue with `runner.load`. This is because `declare` commands will resolve when the dialogue loads (as opposed to when `runner.run()` is called)**

Above, we set an initial value for the `hello` variable, so if a line of dialogue contains `{$hello}`, it will show the number `1`, no need to call `<<set $hello = 1>>`.

Simple dialogues can probably just use the built-in storage.


### Functions

// Advance the dialogue manually from the node titled 'Start'
const d = runner.run('Start')
let result = d.next().value;
let nextResult = d.next().value;
// And so on
You can also register functions to be used in your dialogue.

```javascript
runner.registerFunction('sayHello', () => 'hello')
```

If a line of dialogue contains `{sayHello()}`, it will show `hello`.


### Object Input Format

In addition to the regular yarn format as a string, bondage also accepts a javascript object. This is an intermediary format exported by some utilities. The text format is nicer to work with, so it should be preferred. For reference,

```
#someFiletag
#someOtherFiletag
title: SomeNode
tags: hello
arbitraryKey: arbitraryValue
---
This is a line of text
<<jump SomeOtherNode>>
===

title: SomeOtherNode
---
This is another line of text.
===
```

For usage of the yarn format itself, please see the [YarnSpinner Documentation](https://github.com/thesecretlab/YarnSpinner/tree/master/Documentation), everything there should carry here too (if something does not match up, please open an issue).
is equivalent to:

```javascript
[
{
"title": "SomeNode",
"tags": "hello",
"arbitraryKey": "arbitraryValue",
"body": "This is a line of text\n<<jump SomeOtherNode>>\n",
"filetags": [
"someFiletag",
"someOtherFiletag"
]
},
{
"title": "SomeOtherNode",
"body": "This is another line of text.\n",
"filetags": [
"someFiletag",
"someOtherFiletag"
]
}
]
```


# Other included versions

A minified version exists at `bondage/dist/bondage.min.js`.

If you want to transpile for yourself, use `import bondage from 'bondage/src/index'` and make sure it's being included by your build system.

If you need compatibility with internet explorer, you can transpile for yourself or use `bondage/dist/bondage.ie.js`.


# Development

The parser is compiled ahead of time, so after making changes to the grammar you will need to run `node src/parser/make-parser`. This is done automatically during `npm run build`.

2 changes: 2 additions & 0 deletions Extensions/DialogueTree/bondage.js/dist/bondage.ie.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
Loading