Giter Site home page Giter Site logo

y-lohse / inkjs Goto Github PK

View Code? Open in Web Editor NEW
485.0 24.0 100.0 12.84 MB

A javascript port of inkle's ink scripting language.

Home Page: http://www.inklestudios.com/ink/

License: MIT License

JavaScript 2.15% HTML 0.11% CSS 0.13% TypeScript 93.46% Ink 4.16%
inkjs story game-development

inkjs's Introduction

inkjs

build npm codecov

This is a javascript port of inkle's ink, a scripting language for writing interactive narrative.

inkjs is fully compatible with the original version, has zero dependency and works in all browsers and node.js. Please have a look at the demo!

Table of content

Installation

Install using npm install inkjs.

If you are not using npm you can grab the latest release directly from here. Simply include that file with a script tag and you'll be on your way!

For projects targeting older browsers that have no support for ES2015 features, a (heavier but) more backward compatible version is also exposed. Grab it by either:

  • import ink from 'inkjs/dist/ink.js
  • Directly downloading the file from here

Quickstart

The simplest way to get started with inkjs is to use the serverless boilerplate in the templates folder. Replace the placeholder story in story.js with your own and open index.html!

Here's what happens behind the scenes: inkjs gives you access to a global object named inkjs which has a property called Story. This is the main class we interact with.

We simply create a new story by calling var story = new inkjs.Story(storyContent); — the variable storyContent is defined in the story.js file. After that, we can use story.Continue() and story.currentChoices as described in the the official documentation.

Working with a JSON file

If you frequently need to update your story, pasting the content into story.js will probably get tedious. So another option is to dynamically load the JSON file for your story. Unfortunately, your browser won't let you do that because of CORS policy, which means you need a web server to do this. You could do this without much hassle with node.js or python for example.

Once the server is running, use the other boilerplate and place your story content inside story.json. Behind the scenes, the only difference is that we load the JSON file via ajax before creating the story:

fetch('story.json')
	.then(function (response) {
		return response.text();
	})
	.then(function (storyContent) {
		story = new inkjs.Story(storyContent);
		continueStory();
	});

Using node.js

You can find some boilerplate code for node.js here.

Loading inkjs

Require the module: var Story = require('inkjs').Story;.

Loading a json file

You can load the json file using a simple call to require:

var json = require('./ink_file.json');

You can also load it using fs. In that case, please note that inklecate outputs a json file encoded with BOM, and node isn't very good at handling that.

var fs = require('fs');
var json = fs.readFileSync('./ink_file.json', 'UTF-8').replace(/^\uFEFF/, ''); //strips the BOM

Starting a story

Now that you have a Story object and a json file, it's time to bring it all together:

var inkStory = new Story(json);

console.log(inkStory.ContinueMaximally());
//etc

From there on, you can follow the official documentation.

Differences with the C# API

There are a few very minor API differences between ink C# and inkjs:

On platforms that do not support ES2015 Proxies (basically node.js v5, IE 11, Safari 9 and everything below), you can't directly read and write variables to the story state. Instead you will have to use the $ function:

_inkStory.variablesState.$('player_health', 100);
//instead of _inkStory.variablesState["player_health"] = 100;

var health = _inkStory.variablesState.$('player_health');
//instead of var health = _inkStory.variablesState["player_health"];

Getting the output text when calling EvaluateFunction

EvaluateFunction() lets you evaluate an ink function from within your javascript. The "normal" call is the same than in C#:

var result = EvaluateFunction('my_ink_function', ['arg1', 'arg2']);
//result is the return value of my_ink_function("arg1", "arg2")

However, if you also wish to retrieve the text that my_ink_function output, you need to call it like this:

var result = EvaluateFunction('my_ink_function', ['arg1', 'arg2'], true);
//now result is an object with two properties:
// result.returned is the return value of my_ink_function("arg1", "arg2")
// result.output is the text that was written to the output while the function was evaluated

Using TypeScript

As this library is a port from C#, it requires a less standard way to assign the Story class, including all other classes, to a variable:

import { Story, Compiler } from 'inkjs';

let story: InstanceType<typeof Story>;
let compiler: InstanceType<typeof Compiler>;

Further, to minimize the verbose assignment, you can also create aliases in your project:

import { Story, Compiler } from 'inkjs';

export type InkStory = InstanceType<typeof Story>;
export type InkCompiler= InstanceType<typeof Compiler>;

Compiler

inkjs-compiler.js

$ node inkjs-compiler.js -h

Usage: inkjs-compiler <options> <ink file>
   -o <filename>:   Output file name
   -c:              Count all visits to knots, stitches and weave points, not
                    just those referenced by TURNS_SINCE and read counts.
   -p:              Play mode

online compiler

const story = new inkjs.Compiler(`Hello World`).Compile();
// story is an inkjs.Story that can be played right away

const jsonBytecode = story.ToJson();
// the generated json can be further re-used

You can use this in combination with Webpack and TypeScript.

Differences with the C# Compiler

See Differences with the C# Compiler.

Compatibility table

inklecate version inkjs version json version
0.3.5 – 0.4.0 1.0.0 – 1.1.0 18
0.4.1 – 0.5.0 1.1.1 – 1.1.3
0.5.1 1.2.0
0.6.0 1.3.0
0.6.1 1.4.0 – 1.4.1
0.6.2 1.4.2
0.6.3 1.4.3
0.6.4 1.4.4 – 1.4.6
0.7.0 1.5.0 – 1.5.1
0.7.1 1.5.2
0.7.2 – 0.7.4 1.6.0
0.8.0 – 0.8.1 1.7.1 – 1.7.2
0.8.2 1.8.0 – 1.9.0
0.8.3 1.10.0 – 1.10.5
0.9.0 1.11.0 19
1.0.0 2.0.0 - 2.1.0 20
1.1.1 2.2.0 21

inkjs's People

Contributors

bemisguided avatar chromy avatar dorward avatar ephread avatar erbridge avatar evanwalsh avatar floriancargoet avatar freddiegilbraith avatar hammster avatar iainmerrick avatar joethephish avatar joningold avatar landongn avatar leereilly avatar leopoldtal avatar lptech1024 avatar maetl avatar manuq avatar michael-badrobotgames avatar nqnstudios avatar pineapplemachine avatar renovate-bot avatar renovate[bot] avatar rokasvaitkevicius avatar russellquinn avatar shepard avatar smwhr avatar technix avatar y-lohse avatar zekecato avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

inkjs's Issues

Is Story.ToJsonString() working ?

I encountered this while cross-referencing the JS version of ink...


myStory = new Story(inkFile);
console.log(myStory.ToJsonString());

a.forEach is not a function

You sure it's working? I think a valid "test case" to do for any port: is to first compare that inkFile input matches the ToJsonString() output "exactly" (not too sure if "exactly" needs to be taken literally, or structurally, though..).
btw, i encountered in an attempt to investigate a current problem I have with my Haxe port atm...
Glidias/inkhaxe#1

Tunnels doesn't work..?

The following ink
-> alpha -> bravo -> charlie -> delta -> END

=== alpha ===
This is alpha.
->->

=== bravo ===
This is bravo.
->->

=== charlie ===
This is charlie.
->->

=== delta ===
This is delta.
->->

thows the following err:

ink.js:942 Uncaught TypeError: Cannot read property 'components' of undefined

Reversed output stream

-> start

=== function get_stalk_time ===
{
- time == 0:
    ~ return "12 AM."
- time < 12:
    ~ return "{time} AM"
- time == 12:
    ~ return "12 PM."
- else:
    ~ return "{time - 12} PM"
}

=== start
VAR time = 11
{ get_stalk_time() }
-> END

The following example should produce 11 AM, but produces AM11 instead. The problem starts around here.

Ping @pineapplemachine who found this one.

SequenceHash implementation

Is building sequenceHash impl in Story.js same as the C# version?
https://github.com/y-lohse/inkjs/blob/master/engine/Story.js#L1205

Your impl doesnt appear to break anything, but I'm wondering Issit the proper way to do it.. The C# version does it as a int sequenceHash=0; sequenceHash+=c; // where c is a char. What exactly happens when you add char to the int var for C#?? I suspect the char is type coerced to ascii int charcode? I'm not too sure. Also, is the char-int result added to the int hash via math addition or contatenated like a string?

Compatibility with older browsers?

I notice that you use a lot of the newer JavaScript features in inkjs, and was wondering whether we know what the impact of that might be, if users want to upload content onto the web?

I'm vaguely aware of Babel... I wonder if it would be possible / sensible to use it to compile a version of inkjs that's as compatible as possible?

I'm thinking specifically about inky's "Export for web" option that I've built and will be in the next release. It's going to export a simple web template that includes the iife version of inkjs, and I want to make sure that the result works in as many browsers as possible.

ink 0.7.3 update

ink 0.7.3 has quite a few improvements, but just one small one that requires an update from inkjs for the runtime. See this changelist:

inkle/ink@798f512

Path.js code comment

return otherPath.components == this.components; //originally uses SequenceEqual, not sure this achieves the same

https://msdn.microsoft.com/en-us/library/bb348567(v=vs.110).aspx

I don't think it does. You'd need to enumerate through both and compare each element within the components list and ensure the entire sequence matches. I safely assume the "default equality operator" should be referential equality (==) with the Component objects, and not necessarily the IEquatable interface implementation Equals(other:T) (that's used for something else..i think), but you might want to check with the actual devs.

Anyway, doc reference for IEquatable .
https://msdn.microsoft.com/en-us/library/ms131187(v=vs.110).aspx
I think this is mainly used for ObjectMaps like Dictionaries and such with regards to Key identity...but I can't find any reference of it being used explicitly for the IEnumerable implementations like List. Which was why I safely assumes that simple "==" should be fine... since I don't see any call made to SequenceEquals() with the optional parameter IEqualityComparer included in. Anyway, I myself just want to to be sure, because some of the "==" comparisons being made within the C# code lines might need to explicitly call the "Equals" method directly for other platforms, but I might be wrong.. One important thing is to not gloss over the operators functions in some classes, as you'd likely have to call these functions instead of using the standard code operators. (however, the operator implementations they used with regards to the Object class appear to be fairly trivial and the ecmascript equilavents should be fine..). In Haxe, anyway, there is no implicit boolean conversions, so some of the C# syntantic sugar they adopted with operator methods to circumvent the need to explicitly declare operator comparisons when checking for Runtime.Objects, doesn't apply for me. Also, with the explicit !=, == operators, I find it strange they represent != operator as !(a == b) instead of (a!=b), which makes me wonder...is there a difference? If there is, I might have to run a check and use the function definition instead, but as of now, i think typical equality operators should do fine since those methods aren't doing anything really fancy,

Anyway, just commenting/cross-referencing against yours as I'm doing up a Haxe version of this.

Rewind story, like in inky editor?

At a couple of points in my script I'd love to be able to give players a chance to 'do over' a section from some point before the last choice they made.

The naive fix was to use a divert to get back to the desired point. The trouble is that the previously chosen choices are no longer available of course, while i'd like them to be (as if time had been rewound). And I don't want to make them sticky because in other paths the player takes, the choices should disappear.

I wondered if there's currently an ability to 'rewind' the state of the story in inkjs as there seems to be in the inky editor. Failing that is there some other approach i should look into? (some kind of save and restore routine?).

Thanks for this great library!

Viewcount and choices

Great progress! In my tests, one-shot choices are displaying repeatedly, apparently because it is not being noticed that the target has already been visited.

Implement StoryException

Currently all errors are just plain throw declaration, whereas the reference implementation mostly uses a StoryException. We should mimick that for better catchability.

Can't seem to process comparison of strings!! val == "stringVal"

Can't seem to process comparison of string

Run this method and see

->Testing

=== function testingMethodStringRef(offhand)
{offhand}
{ 
    - offhand == "gladius":
        got gladius
    - else: 
        sorry
}
done

=== Testing
~testingMethodStringRef("gladius")
->DONE

If i remove off the comparison, it runs ok...

Use private members when possible

In a lot of places, we're using the public getters and setters when actually inside the class. I haven't checked but it's almost certainly slower and often pointless.

Fix HasFunction & EvaluateFunction

Both these functions fail atm, because the function can not be found. I still have to investigate more, but Container is looking into the named contents, but finds nothing.

StringBuilder class

The C# version uses a StringBuilder classes in a lot of places. I've used simple strings everywhere and it works ok, but in a few cases (mainly #9 and #29) this isn't enough, because we need to be able to pass the variable around as a reference.
Here's the C# version.

Diverting to knots starting with a number

The following ink script doesn't work as expected:

start
-> 1targetknot
-> END

==1targetknot==
in knot
->DONE

As in, it produces no content and just hangs there.

Here's a milder version of the bug:

-> top_knot

==top_knot==
start

* option 1 -> 1targetknot

-> DONE

==1targetknot==
in knot
->DONE

External functions get called multiple times

It appears that the following calls wait twice - once on each line.

Hello world!

{ wait(10000) }

How are you today?

The following has the desired effect of only calling wait on a single line.

Hello world!{ wait(10000) }

How are you today?

Error on ObserveVariable()

Hi,

registering variable observers like

    myStory.ObserveVariable("health", (varName, newValue) => {
        console.log(varName + ' changed to ' + newValue);
    }); 

works fine, as long I'am registering observers for every variable which changes its value.

Having 2 variables, but only register a observer for one variable, I get the error:

Tried to get the value of a variable that isn't a standard type
from
Story.VariableStateDidChangeEvent(...)

Then things get complicated and I'am still trying to figure out, how to load und debug your code unpacked.

Registering multiple observers for one variable works fine again. Thanks for your great work.

Harmonize exceptions

I just noticed I've been a bit inconsistent when it comes to exception. Most of the time I only throw "normal" ones, but every now and then I use custom ones. This should be harmonized to fit the C# version.

Simple story produces no content

The following ink:

VAR x = false
{x:
    false
- else:
    true
}

...should print true. However when inkjs loads the json, it doesn't print anything.

Here is the JSON:

var storyContent = {"inkVersion":15,"root":["G>","ev",{"VAR?":"x"},"/ev",[{"->":"4.b","c":true},{"b":["^false","\n",{"->":"6"},null]}],[{"->":"5.b"},{"b":["^true","\n",{"->":"6"},null]}],"nop","G<","\n","done",{"global decl":["ev",0,{"VAR=":"x"},"/ev","end",null],"#f":3}]}

(ink version 0.6.1, inky version 0.6.0)

Cannot get globalTags

In Lectrote, I am trying to get the globalTags to extract the game title, but I get an exception:

Uncaught TypeError: Cannot read property 'every' of undefined
at i.value (/Users/zarf/src/lectrote/inkjs/ink.min.js:3:7808)
at i.get (/Users/zarf/src/lectrote/inkjs/ink.min.js:3:12111)
at :1:6

The code is just

story = new Story(str);
console.log(story.globalTags);

user session and async play?

Hi there,

finding out about inkjs just made my day so a huge thank you to everybody working on this.

I'm trying to build an online game with ink and I need some way for players to register and save/resume their games. Is that something that is possible or even make sense to be in inkjs? Do you have any recommendation on how to handle this?

If it makes any sense, ideally I'd have inkjs just be the story engine/API (that's what I was trying to build). Reading about ink it seems that's how it's designed to begin with so that it can be plugged into Unity etc. What I'm envisioning is basically a state machine that can receive commands from whatever input (web user clicking, email, facebook bot) and advancing to whatever section based on the story + action. Does that make any sense? am I just way off track?

thank you in advance and happy 2017,

Spike

Threads aren't working?

Testing a story of mine, threads don't seem to be working -- no choices appear, even though choices are supposed to be gathered from the threaded nodes.

It works in Inklecate's command-line player, so it's not an issue with my code.

Proxify the VariablesState

In order to get and set VariablesState, we are currently using an added $ function, instead of a regular getter and setter. We should be abble to mimick the reference implementation by using an ES6 Proxy, but that may cause some compatibility problems. Investigate and maybe put that option behind a compiler flag.

Port the compiler

This port currently has the engine part ported. Having the compiler (inklecate) would be cool, but it's probably a big endeavour.

funcResult.GetType() doesn't guard against GetType being undefined.

I'm experimenting with external functions, in particular just echoing back input, which has all kinds of confusing behavior currently. (For example, you can get a Path by passing in a divert, but I can't figure out how to send it back. For another example, returning strings is weirdly finicky.)

Many of the issues seem to come from funcResult.GetType(), currently on line 904 of Story.js, throwing an error because funcResult.GetType is not a function. This prevents the warning from being printed, and halts execution.

Test suite

Being a port, this project is in dire need of a test suite to ensure that it is compatible with the reference implementation.

Right now there are a bunch of manual tests and contributions to those are welcome, but ideally we need an automated test suite. There is one in the reference repo and it would be really awesome if it could be ported as well. However I think it requires compiling ink files, which might be tricky at the moment.

Fix BuildStringOfHiearchy

The BuildStringOfHiearchy is currently broken because there is no StringBuilder class in javascript that can be passed around as a reference. It would probably be cool to create a very small one and use it at least there.

Visit counts not incrementing correctly

The following ink source generates different results in inky and in ink-js after web export.

-> game

=== game 
= main 
    -> top 
= top 
    COUNT = {top}
    + Loop -> main

In particular, the ink-js version does not increase the value of "top" as printed on the COUNT = line.

EvaluateFunction text output

EvaluateFunction returns whatever the function returns, but it is also using the out keyword to return potential output text. Since there is no out in js, I need to find a way around this.
Note that this requires a StringBuilder class anyway.

Add a LICENSE file?

I don't see a LICENSE file on the repository. Would it be possible to add one to make the open-source rights clear?

Minify the compiled versions

The compiled source is already fairly large. It be good to minify it before it gets published. This should be implemented as an option though, because we still need the full source for debuging pruposes. We probably need sourcemaps too.

Retrieve tags for choices?

I'm not sure if I'm supposed to be able to do this. What I'd like to is check for the presence of tags on choices in my script so that I can render some choices styled differently.

I haven't been able to figure out how to retrieve tags at the end of choices in my ink script. For instance, if i'd like to check for the presence of the #danger tag to have the third choice styled differently, how could I do that?

* Choice one
* Choice two 
* Dangerous choice three #danger
* Choice four

Default parameters

I've changed my mind a few times about how default parameters should be implemented, so there are some discrepencies there. It'd be good to make them all the same. Ideally using ec6.

Release version invokes babelHelpers

When I load ink.min.js, it breaks because it's trying to invoke babelHelpers.classCallCheck(). That's a mistake, right? babelHelpers is listed as a dev dependency only.

Tutorial on how to use inkjs?

I'm trying and failing to use inkjs to make a webpage for an ink I wrote. I feel like I must be missing something obvious, and some steps to follow for getting things to a workable state would help to at the very least narrow down why I'm getting the errors that I am.

Thanks!

Division is not working as specified

The following ink:

{2 / 3}
{7 / 3}
{1.2 / 0.5}

should produce:

0
2
2,4

but instead inkjs produces

0.6666666666666666
2.3333333333333335
0

I can understand how inkjs might simply treat all numbers as floats, but that last one puzzles me.. How does 1.2 / 0.5 = 0 ..?

external funcions not working

This is my ink:

-> start
=== function get_time() ===
    ~ return "(no external function found)"

=== start ===
It is {get_time()}.

-> END

I export it to web and add an external function in main.js :

var story = new inkjs.Story(storyContent);
story.BindExternalFunction("get_time",     function(){ return "twelve o'clock"; });

but no matter where I run it, the external function doesn't work

It is (no external function found).

There are no errors in the console.

Fail on Assert?

The original code uses a bunch of Asserts. I think that if they fail, execution is supposed to stop, which isn't the case in the js version. All asserts have been replaced with calls to console.warn so they should be fairly easy to track down.

TypeScript definitions?

I'm trying to write TypeScript definitions for inkjs, in an attempt to integrate it with a toolset that uses TS, and I'm really not sure where to start. I got as far as declare var Story; and got stuck.

Any pointers?

Returning an unboxed string value from an external function can fail.

I hit this in the same experimentation that led to #26. Casting from strings to StringValues is extremely finicky, in ways I don't quite understand. It appears that round-tripping a string from Ink to js to Ink works, and the string looks like a normal string in the console. But if I return a string defined in JavaScript from an external function, then it will fail unless I use new String("foo"), apparently because "foo" instanceof String is false.
I don't know JavaScript well enough to understand how this behavior is even possible, but it happens.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.