Skip to content

Releases: inkle/ink

Version 1.2.0 - Highland release

10 Jun 11:03
Compare
Choose a tag to compare

With the release of A Highland Song behind us, we have a selection of minor updates, fixes and improvements that brings ink to version 1.2.0, otherwise known as the Highland release!

  • It’s now possible to have a newline between a choice’s label and its content, which is helpful for laying out verbose looking choices:
    * (say_something_interesting_about_bricklaying)
        "I did have one interesting fact about bricklaying, if you don't mind me spending taking a fair bit of time to lay the groundwork for it."
  • Slight API improvements to Ink Lists to make them a little easier to work with on the game side.
  • Fix for errors when attempting to load a new version of story during development after major changes (Thanks @russellquinn)
  • Fix for choices that were storing thread references in a non-safe way, meaning that it could cause corruption during a background save.
  • Fix for choice tags not being serialised (Thanks @hizzz!)
  • Fix to allow variable observers to run new ink more robustly by having the callback fire later
  • Support for using Continue() to complete a previously active ContinueAsync()
  • Runtime is now targeting net standard (Thanks @paulloz!)
  • Fix for compiler failing when executed as a process within Unreal Editor 5 (thanks @TCR-Nick!)
  • Chinese character ranges now supported for identifiers (thanks @vvjiang!)
  • Other minor fixes and improvements (thanks @RobertJaskowski, @Meorge, @dgileadi)

1.1.1

18 Oct 14:01
Compare
Choose a tag to compare

Version 1.1.1

Note: we previously released version 1.1 but it was a bit buggy so we're immediately replacing it with 1.1.1!

First, a little tip: We strongly suggest never updating to a major new version of ink for a game that's publicly released! While we aim for backward compatibility, we don't absolutely guarantee stability between versions.

Now, on to the fun part:

New feature: Dynamic tags, and tags in choices

A big thanks to BetterUp for sponsoring this feature.

You can now write ink rather than just plain text within tags:

VAR subject = "dog"
This is a picture of a {subject}. # image_of_{subject}.jpg

You can also directly use tags within choices, in all the places you can use text within choices. Here's a simple example:

+ [Take the apple # hand_holding_apple.jpg]

Or to combine the two above features:

+ [Take the {thingName} # hand_holding_{thingName}.jpg]

And here's an example that uses tags in every spot within the choice:

+ Start of choice text # tag both [Choice only text # choice only tag] This is after the choice is taken # post choice tag

On the game code side, the choices themselves now have a list of choices available on them too:

public class Choice : Runtime.Object {
    ...
    public List<string> tags;
    ...
}

Other changes

  • BREAKING CHANGE: Doing someList ? () (i.e. specifically checking for a list containing an empty list) now returns false rather than true, since this makes more logical sense in most circumstances and reduces the amount of explicit checks for that case.
  • Update to .NET 6.0
  • Documentation fixes (thanks @deviant-dev, @bitbutter, @tomkail, @owennewburn, @Atlas48 )
  • inklecate no longer outputs BOM in output (thanks @Fendse)
  • It's now possible to use ->-> some_variable_divert_target.
  • Add some multi-flow utility functions (thanks @russellquinn)
  • Various external functions and list API improvements (Thanks @tomkail)
  • Improve plugin API (Thanks @elliotherriman)
  • Fixes for origin names being wrong on lists

New ink unity integration plugin is also on its way, hopefully within the next day or two...

Version 1.0.0

22 Feb 16:03
Compare
Choose a tag to compare

ink has finally reached version 1.0!

We realise that it took us so long to get to this point, and in particular that it's been such a long since our last release. Game development can be very intense, so simultaneously maintaining a project such as this one at the same time can be a little tricky.

However, the great news that happened in November 2020 is that we become a proud recipient of an Epic MegaGrant, which will hugely help in the support of the development of ink.

Here are the release notes for version 1.0. There are major new features, though they're mostly on the game side of things rather than the format or writing side of things:

Major new feature: Multiple parallel flows (BETA)

It's now possible to have multiple parallel "flows" - allowing the following examples:

  • A background conversation between two characters while the protagonist talks to someone else. The protagonist could then leave and interject in the background conversation.
  • Non-blocking interactions: you could interact with an object with generates a bunch of choices, but "pause" them, and go and interact with something else. The original object's choices won't block you from interacting with something new, and you can resume them later.

The API is relatively simple:

  • story.SwitchFlow("Your flow name") -- create a new Flow context, or switch to an existing one. The name can be anything you like, though you may choose to use the same name as an entry knot that you would go on to choose with story.ChoosePathString("knotName").
  • story.SwitchToDefaultFlow() -- before you start switching Flows there's an implicit default Flow. To return to it, call this method.
  • story.RemoveFlow("Your flow name") -- Destroy a previously created Flow. If the Flow is already active, it returns to the default flow.

For now it should be considered a beta feature, since it's brand new and relatively untested. We will also be keeping an eye on how people make use of it to see if API improvements can be made.

ink now has a proper bool type

  • Booleans are now supported natively in-engine, but they also implicitly coerce to integers to support old behaviour of true + 1 == 2. This is useful when you want to convert a flag (have you done X) to a counter (how many times have you done X).

Significant technical improvements:

These are significant improvements, however they may cause breaking changes to your existing setup:

  • Better EXTERNALs behaviour: Many people were having issues where their EXTERNAL functions were unexpectedly called twice. This was because of the way the story may sometimes look ahead to see what's coming so that glue can function properly. We have now changed the behaviour so that by default it will do exactly what you expect and only ever be called once. However, if you need your functions to be compatible with glue functionality, you can pass the new lookaheadSafe parameter when you bind a function. For more on this, see the documentation, and original discussion of the issue.
  • Better error handling: Redesign the way error handling works, so that it's not possible to accidentally ignore errors by default. Previously the Story would silently generate errors that you were supposed to check from story.hadError and inside story.state.currentErrors but it was far too easy to forget to do this when starting a new integration into a game. The new behaviour is that you need to bind a callback to story.onError. If you don’t, it will throw an exception on error, so you’ll definitely see it.

Other improvements

The rest of the changes are primarily technical and bug fixes:

  • New: InkList.FromString(shortName, story) function
  • New: Events in runtime story: onDidContinue, onMakeChoice, onEvaluateFunction, onCompleteEvaluateFunction, onChoosePathString. These aren’t recommended as the general approach, but in some cases they are necessary - for example ink-unity-integration’s player window
  • New: ink engine and inklecate now uses dotnet core (Thanks @13xforever!)
  • Support for work-in-progress ink language server by @ephread: Support for character ranges in debug line metadata. Use identifier type rather than plain strings internally in compiler so that you can find a location where a symbol was defined.
  • All internal members have now been marked public. Although this is a little risky and I liked the smaller API footprint, I’d rather have the engine more easily hackable/dangerous than blindly prevent people from using certain functionality.
  • Stats switch on command line compiler: “-s”. Designed for use in Inky’s “word count and more” option.
  • New JSON communication protocol in inklecate for more robust integration with Inky.
  • Added overload of BindExternalFunction that takes 4 parameters
  • Allow passing ink list as function argument
  • Unicode character ranges: support for Korean (thanks @jungeunjin-dev), Latin1Supplement (thanks @landryyrdnal)
  • Fix for broken not-equals operator on divert targets. (thanks @russellquinn)
  • Fix for rolling back state after having followed a default choice 😱
  • Fix for integer overflow when calling RANDOM
  • Fix for error in the profiler when current pointer is null
  • Prevent super large integers from being successfully parsed yet coming out as zero
  • Fix for visit counts of choices/gathers being wrong when they're in a container nested as the first container of another container
  • Prevent storing a divert target to a knot/function that takes by-reference arguments since it’s not possible to call correctly at runtime
  • Ensure keys are removed from _variableObservers if they are empty. (thanks @mdorr)
  • Observer param in RemoveVariableObserver now optional (thanks @michael-badrobotgames)
  • Improved some error messages

Version 0.9.0

10 Jun 09:59
Compare
Choose a tag to compare

First post-Heaven's Vault release! And as such, this mainly brings in features that we developed while working on Heaven's Vault.

  • Shuffle-once and shuffle-stopping: New variants for shuffle sequences that allow you to change what happens at the end of a shuffled sequence. Adding 'once' does nothing when the sequence is finished (can be written ink {shuffle once: ...} or abbreviated {~!: ...}. Meanwhile, adding 'stopping' repeats the final branch of a sequence when the shuffle is done: {shuffle stopping: ...} or abbreviated: {~$: ...}.
  • Background saving of state: If you have a lot of state to save, it's now possible to save on a separate thread (preventing frame drops), and while continuing to evaluate the story on the main thread. It can do this safely by making the main state read-only while saving, and writing any state changes to a temporary "diff patch", which is applied when the save thread is complete. Call var stateToSave = story.CopyStateForBackgroundThreadSave() and then when you're done call story.BackgroundSaveComplete().
  • More efficient/faster main runtime (when calling Continue). When the engine looks ahead for whitespace it previously had to do a lot of copying/restoring the entire state. Now there's a patching system so that only small "diff patches" get created during evaluation - much faster!
  • Saving story state faster and memory efficient (generating less garbage). Faster when variable values are still set to their initial defaults - it only saves out those that have changed.

Unity integration package available here

Version 0.8.3

05 Jun 06:24
Compare
Choose a tag to compare

Bug fixes! See the full list of changes by browsing the changes on github.

Version 0.8.2

16 Aug 06:47
Compare
Choose a tag to compare

Ink unity integration package coming soon!

  • Added LIST_RANDOM(list): return a random item from a list
  • Added TURNS(): Which turn number are you on? i.e. how many choices have you taken? (starts at zero)
  • Added POW(x, y): Mathematical function to calculate "x to the power of y".
  • Added FLOOR(x): Round down to the nearest whole number, returning a float (or doing nothing if int is passed).
  • Added CEILING(x): Round up to the nearest whole number, returning a float (or doing nothing if int is passed).
  • Added INT(x): Cast float to an int (truncating).
  • Added FLOAT(x): Cast int to float.
  • Repurpose LIST_RANGE(list, min, max) to use a variable list rather than a list definition. To get the previous behaviour, use LIST_RANGE(LIST_ALL(list), min, max). Min and max can either be the int value of list items, or they can be the list items themselves - e.g. LIST_RANGE(cakes, FrenchFondant, ChocolateGateaux) or LIST_RANGE(cakes, 3, 6).
  • {myList:...} now means "if myList contains any items" instead of "if myList contains a non-zero valued item".
  • Better naming collision detection (e.g. between stitch names, checking for reserved words and built in functions)
  • Add string “not contains” operator: “hasnt” and “!?” for checking existence of substrings.
  • Add support for != operator on divert targets.
  • Compiler code tweaks so that it can be included in Unity code (so you can compile ink stories live in your game for user contributed stories etc). Thanks @KumoKairo!
  • Support for pasting a runtime path in the "Go to anything..." box in inky, useful when you have a bug in Unity and only have a runtime path: Play mode in compiler can now take a query of the form DebugPath hello.world.0.1 for looking up runtime paths and returning line numbers from original source files.
  • Fix for major long-standing read count bug, where read counts sometimes weren't correctly incremented when diverting to a gather or choice label if it was already close to the label at the point it diverted.
  • Fix for choices having broken threads when saved/loaded
  • Fix for excessive inline whitespace.
  • Fix for functions being called on tilda logic lines not being appended with a newline when they need one.
  • Fix for RemoveVariableObserver.
  • Fix for a divert target being used as a function parameter incorrectly being seen a normal divert, and therefore being incorrectly ignored as a loose end: e.g. + choice {f(-> g)}
  • Fix for compiler crash when target not found during tunnel onwards (e.g. ->-> onwardPathNotFound)
  • Warn, don’t crash, when there’s a duplicate list item in a list. Error when there's a duplicate item in a list definition.
  • Give sensible error when attempting to pass a knot to a function that takes a variable reference, instead of crashing
  • Clearer error when reporting missing variable - tell us which variable was missing!

Version 0.8.1

24 Apr 11:41
Compare
Choose a tag to compare

Version 0.8.0 is a major ink update, including lots of improvements and fixes under the hood! This is version 0.8.1 which includes a few bonus extra fixes. Combined 0.8.0 and 0.8.1 release notes:

ink language changes

  • "Loose end" detection is now far more robust. Previously it wouldn't detect loose ends in even in simple choices, only in a sequence of "flat" content. (#416)

  • White-space handling is a bit different since handling was re-written in the engine (see below). Most obvious difference the following: previously it would be one line, whereas now there will be a newline inserted:

      Hello {true:
          ...world.
      }
    

    Version 0.8.1 also includes this fix.

  • Temporary values may not be accesssed by stitches from their parent knot, or across stitches. For example, this used to be possible, but is no longer allowed:

      == knot ==
      ~ temp x = 5
      -> stitch
      
      = stitch
      {x}
      -> END
    
  • Writing not A ? B will show a warning, since you almost certainly want not (A ? B) rather than (not A) ? B, but order of operations makes that impossible.

  • Using the increment and decrement operators (x++ and x--) is now much stricter - you're only allowed to use them on their own, and not inline, so only ~ x++ is allowed.

  • Version 0.8.1 also fixes threaded choices being broken in the global/top level flow.

  • Version 0.8.1 does type checking for divert targets being accidentally passed as read counts (see #425).

And, as always, there's a smattering of bug fixes and smaller improvements!

Game- and engine-side major changes

  • Big improvements in how ink can cope with saving/loading a save state when the ink story has been changed. It will never be possible to make this perfect, so you should always exercise caution, especially when editing content for a game that's in production. But it shouldn't be quite so fragile anymore! (#195)
    • If a variable is added, it will no longer crash when it accesses the value from a save state. It will try to use the original declared value. If you try to access a temporary variable that doesn't exist (yet) it will return the default value of 0 (false).
    • If you try to call ChoosePathString on a path that doesn't precisely exist, or make a choice that diverts to a target that no longer exists in a new version of the ink content, the engine will do its best to work its way up the path and find content that does exist. If it has to do this, it will create a runtime warning (see next point).
  • Story objects now have a currentWarnings list as well as currentErrors. It's very important that you pay attention to (logging) both of these lists whenever you call Continue, or you could be missing out on vital debugging information.
  • story.ContinueAsync(millisecs) - major game-side feature. New version of Continue() that allows you to specify a budget of a maximum amount of time you want to allow the engine to execute for before it pauses and allows you to finish another time. Great if your story sometimes goes into some complex logic, and you only want to allow it to execute for a few milliseconds per game frame. You can use the story.asyncContinueComplete property to detect when it's finished. Obviously this feature needs to be used with caution, since the story will be in an unpredictable state in the middle of execution. Although we have tried to guard against certain operations, it may still possible to accidentally get into sticky situations if you're not careful!
  • The compiler has now been moved out into a separate DLL (ink_compiler.dll), making inklecate a very lightweight wrapper around it. This should make it much easier for you to compile ink scripts from your own C# code (e.g. for game modding.)
  • story.ChoosePathString now takes a resetCallstack parameter that defaults to true. This means that if you were in a tunnel (or multiple) when calling ChoosePathString, then by default it'll take you back out again. If you pass false to resetCallstack then it'll preserve the state of the tunnel(s). It defaults to true since the assumption is that usually you use ChoosePathString to entirely change scene / go to an entirely new section.
  • Re-write the way EvaluateFunction calls from game code work in the engine to be more robust, and allow them to make multiple nested calls from Game -> ink -> game -> ink for very complex logic!
  • Re-write the way that whitespace is handled - see ink language changes above for an example of how this has subtly affected the language. Previously the compiler inserted "single-direction implicit glue" at the start and end of anything enclosed in { and } (inline logic). This wasn't robust since it was possible to divert out of the logic, which would "leak glue" since the left/right portions didn't match. Instead, newlines are now automatically consumed at the start of function calls, which makes the story lighter weight, and the whole system more robust.
  • Optimisation to the way the engine steps through the content.

Unity users: What should you download?

If you're using Unity, the UnityInkIntegration package is all you need!

Inky download

For Inky, the ink editor, download the latest version (0.9.1) here.

Version 0.8.0

19 Apr 16:44
Compare
Choose a tag to compare

A major ink update, including lots of improvements and fixes under the hood!

ink language changes

  • "Loose end" detection is now far more robust. Previously it wouldn't detect loose ends in even in simple choices, only in a sequence of "flat" content. (#416)

  • White-space handling is a bit different since handling was re-written in the engine (see below). Most obvious difference the following: previously it would be one line, whereas now there will be a newline inserted:

      Hello {true:
          ...world.
      }
    
  • Temporary values may not be accesssed by stitches from their parent knot, or across stitches. For example, this used to be possible, but is no longer allowed:

      == knot ==
      ~ temp x = 5
      -> stitch
      
      = stitch
      {x}
      -> END
    
  • Writing not A ? B will show a warning, since you almost certainly want not (A ? B) rather than (not A) ? B, but order of operations makes that impossible.

  • Using the increment and decrement operators (x++ and x--) is now much stricter - you're only allowed to use them on their own, and not inline, so only ~ x++ is allowed.

And, as always, there's a smattering of bug fixes and smaller improvements!

Game- and engine-side major changes

  • Big improvements in how ink can cope with saving/loading a save state when the ink story has been changed. It will never be possible to make this perfect, so you should always exercise caution, especially when editing content for a game that's in production. But it shouldn't be quite so fragile anymore! (#195)
    • If a variable is added, it will no longer crash when it accesses the value from a save state. It will try to use the original declared value. If you try to access a temporary variable that doesn't exist (yet) it will return the default value of 0 (false).
    • If you try to call ChoosePathString on a path that doesn't precisely exist, or make a choice that diverts to a target that no longer exists in a new version of the ink content, the engine will do its best to work its way up the path and find content that does exist. If it has to do this, it will create a runtime warning (see next point).
  • Story objects now have a currentWarnings list as well as currentErrors. It's very important that you pay attention to (logging) both of these lists whenever you call Continue, or you could be missing out on vital debugging information.
  • story.ContinueAsync(millisecs) - major game-side feature. New version of Continue() that allows you to specify a budget of a maximum amount of time you want to allow the engine to execute for before it pauses and allows you to finish another time. Great if your story sometimes goes into some complex logic, and you only want to allow it to execute for a few milliseconds per game frame. You can use the story.asyncContinueComplete property to detect when it's finished. Obviously this feature needs to be used with caution, since the story will be in an unpredictable state in the middle of execution. Although we have tried to guard against certain operations, it may still possible to accidentally get into sticky situations if you're not careful!
  • The compiler has now been moved out into a separate DLL (ink_compiler.dll), making inklecate a very lightweight wrapper around it. This should make it much easier for you to compile ink scripts from your own C# code (e.g. for game modding.)
  • story.ChoosePathString now takes a resetCallstack parameter that defaults to true. This means that if you were in a tunnel (or multiple) when calling ChoosePathString, then by default it'll take you back out again. If you pass false to resetCallstack then it'll preserve the state of the tunnel(s). It defaults to true since the assumption is that usually you use ChoosePathString to entirely change scene / go to an entirely new section.
  • Re-write the way EvaluateFunction calls from game code work in the engine to be more robust, and allow them to make multiple nested calls from Game -> ink -> game -> ink for very complex logic!
  • Re-write the way that whitespace is handled - see ink language changes above for an example of how this has subtly affected the language. Previously the compiler inserted "single-direction implicit glue" at the start and end of anything enclosed in { and } (inline logic). This wasn't robust since it was possible to divert out of the logic, which would "leak glue" since the left/right portions didn't match. Instead, newlines are now automatically consumed at the start of function calls, which makes the story lighter weight, and the whole system more robust.
  • Optimisation to the way the engine steps through the content.

Version 0.7.4

28 Jun 10:16
Compare
Choose a tag to compare

Small release with a couple of fixes:

  • Fix for using tags on choices, so that the tag doesn't get added to the line after.
  • When cloning state, ensure the _originalCallstack value is copied, for when evaluating functions from C# code.

Version 0.7.3

06 Jun 18:02
Compare
Choose a tag to compare

A solid batches of fixes and improvements.

  • Far more robust detection of naming collisions between various symbol types. You might notice some new errors in your ink that weren't being detected before!
  • Much improved behaviour for default choices (* ->), and disallowed usage of -> outside of this.
  • More divert types supported on choices, so you can do for example * out of the tunnel we go! ->->.
  • The divert taken after a tunnel return can now take parameters, useful for this pattern ->-> where_next(-> egypt).
  • The &&/and and ||/or operators on lists now check whether they contain anything, then do a logical and/or on the results. Previously and used to do the union operation on lists (e.g. cloak and dagger). Instead, you need to do cloak + dagger.
  • Fix for using x++ and x += y within functions, and general increased compiler robustness for them.
  • EXTERNALs can now be defined anywhere. Previously there were errors with using them below knots.
  • New READ_COUNT() internal function that can be used on variable divert targets, which wasn't possible before.
  • Can now store a variable divert target that points to a knot that takes parameters, and therefore get the READ_COUNT / TURNS_SINCE of it. Warning, there's no checking for the number of parameters that a parameterised knot takes when calling it from a variable divert target. If you get it wrong, weird stuff will happen!
  • Ability to query the current stack depth from runtime state (if you use source rather than the DLL, since it's currently internal), useful for debugging tunnels.