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

[Feature proposal] Tags #122

Closed
joethephish opened this issue Jul 10, 2016 · 29 comments
Closed

[Feature proposal] Tags #122

joethephish opened this issue Jul 10, 2016 · 29 comments

Comments

@joethephish
Copy link
Member

There have been a few requirements for some kind of tagging / metadata in ink, and after giving it some thought, I was thinking of something like the following.

The basic syntax is a @ character, followed by a user-string that is terminated by the end of a line.

Global tags

Useful for adding metadata to a (root) ink file at global scope. There would be a way to access a global tags dictionary, or a simple array of strings. author and title are defined by convention rather than being specific syntax - they're just keys, or they're part of the full string following @. They could be used to set up properties when exporting to web from Inky, and when loading up an ink story in @erkyrath's Lectrote.

// Top of an ink file
@author Joseph Humfrey
@title My Wonderful Ink Story

Knot tags

It might be useful to associate arbitrary metadata to knots (and stitches), so that it's possible to query certain properties about them without running them. For example, this could've been used 80 Days to determine the pre-condition for travel to a particular location, to be used on the game layer.

== cairo ==
@location city
@require map_to_cairo

Line tags

Finally, the tags could be generalised to any line at all, with it possible to get a currentTags property at the same time as getting main content from the engine. And tags which are seen by the engine while evaluating will be added to an array. e.g.

Passepartout: Really, Monsieur. @face:surly

...where face:surly is a tag string that can be parsed by the game so that a specific portrait can be displayed.

Currently we're using a # convention, with the string being extracted by the game by hand. And that works fine, but it just seems like a built-in system might be useful more widely.

@ladyisidore
Copy link

This would be excellent and save me a lot of work, because I planned on adding precisely such functionality to PalimpsestNW, since it seems like something I could use in a game I'm working on (especially knot and location tags).

@y-lohse
Copy link
Contributor

y-lohse commented Jul 10, 2016

Same here, I was definitely going to need something like that so i'm quite in favor of adding such a feature.

Why the change from # to @? I guess I'm mostly worried about collisions between game text and tag structure at a line level, ie. "My email is [email protected]", I proudly declared. Not sure how much of a problem that would be.

@joethephish
Copy link
Member Author

joethephish commented Jul 11, 2016

The change from # to @ was simply to still allow fully text-based outside-of-ink-engine tags in the style we've already been using. But yeah it's possibly not the right decision - # is more familiar for tags, and I also like the association with comments in some languages.

Either way, you can escape out the character to have it appear within the game: "My email is yannick\@lol.com", I proudly declared.

@clembu
Copy link

clembu commented Jul 13, 2016

Well I've been working on a Command system for my own games. To add a new command I'd have to only create a new class inheriting my own Command abstract class, add the C# attribute [Command(Name,Pattern,Priority)] and my parser looks up at each new ink line if it matches with any of the commands registered when the game launched (the thing even looks up public fields and properties so you can have parameters in your command pattern)
So I even can have a QUAKE command attached to a command subclass that will shake the camera in its Execute method. My SayCommand currently uses the pattern @"^(?<speaker>\w+)(?:\((?<portrait>[\w-]+)\))?:\s(?<line>.*)$", where the named capture groups are then hooked up to my instance members.

I think it's pretty solid, but the addition of tags could even make it easier. I'm keeping a curious eye on this feature!

@hoverbird
Copy link

This would be massively helpful for me, too. I've been looking for ways to integrate voiceover audio for dialog in Ink, and was looking at implementing a special-case tagging system for it. I'd much rather have this system be general-purpose and baked in to the engine as you propose! Line level as well as knot/stitch level would be ideal. Then I could do something like:

=== cave_conversation ===
HENRY: What's in this cave down here? @wavid:1234

DELILAH: I don't know. Rocks? @wavid:1235

@micabytes
Copy link
Contributor

I've been using something similar, though I've baked the feature into comments in order to limit compatibility issues, e.g. //@img, etc.

@VengantMjolnir
Copy link

I'll throw my hat in the ring as a vote for a tagging system. I like what is proposed in the original post, with the amendment of using # as the tag symbol. However if the choice of # causes problems with legacy Ink scripts, then I'd support using @.

@joethephish
Copy link
Member Author

Yeah, alright, using # sounds good. It's the best symbol for the feature, and it's still easy enough for anyone to use a different symbol for manually parsed tags etc.

@lhughes41
Copy link

Another vote for this feature for reasons of invoking external game actions (commands)

@ladyisidore
Copy link

Is there an ETA on when tags would be implemented? I'm working on something that could greatly benefit from this feature and I'm wondering if I can reasonably wait for it to be implemented or go ahead and make something of my own for the time being.

@joningold
Copy link
Member

Random thought: could you achieve the same effect with external functions?
So like

{location("Paris")}

Or whatever..?

Jon
On Sat, 10 Sep 2016 at 3:19 pm, Isak Grozny [email protected]
wrote:

Is there an ETA on when tags would be implemented? I'm working on
something that could greatly benefit from this feature and I'm wondering if
I can reasonably wait for it to be implemented or go ahead and make
something of my own for the time being.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#122 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AA40o00felD_rGB6V6RI1QZf9sA4ua9Nks5qorx8gaJpZM4JI1g8
.

@joethephish
Copy link
Member Author

@isakgrozny sorry no ETA right now :-( it's quite high on my list of ink stuff to work on, but right now lots of our game stuff is higher.

@joningold the main thing with global and knot level tags is to be able to extract them without actually running content. Was thinking of working on this as a wider metadata feature that might also include source line numbers (if you want them), and function signatures etc. Currently there's no way to validate that you're passing the right number of arguments when calling an ink function from C#.

@joethephish
Copy link
Member Author

Okay, the feature is ready, it's in the tags branch: https://github.com/inkle/ink/tree/tags

This post is mainly to give @y-lohse a chance to catch up with the JS version before I release an update to Inky :-) (no hurry, just let me know when you're ready!)

I went with the # character rather than the @ character. You can have multiple tags per line:

This is content. # tag one # tag two

I decided not to have any formal dictionary structure, but instead just make them strings for simplicity, which echoes the way that ink works in general.

The global and knot tags are actually just normal line tags, but easily accessible "statically" i.e. without running the ink, by peeking into the content. For example:

== knot ==
# a knot tag
The knot content # another knot tag
# final tag

In C# if you call story.TagsForContentAtPath("knot"), the result will be a List<string> containing "a knot tag" - i.e. just the "header tag".

However, when running your ink, if you divert into the knot and call Continue() once, then story.currentTags will give you a list that contains the initial "a knot tag" and "another knot tag" since it's associated with the line that was just generated.

Finally, since it's on a separate line that comes after a valid line of content, you'll have to call Continue() again, and story.currentTags will contain "final tag".

@y-lohse
Copy link
Contributor

y-lohse commented Sep 28, 2016

Thanks for the heads up! And the engine changes seem quite small (again!) so that's good to see.
I'll likely have a chance to work on it next week, I'll keep you posted :-)

@joethephish
Copy link
Member Author

I'd suggest we agree on a standard global tag format convention for basic story metadata. This won't be useful for many circumstances since ink is designed primarily as a single layer in a stack. But, it's good to have nonetheless - my proposal is simple:

# title: The Story Title
# author: The Author Name

(So the strings extracted by ink will be literally "title: The Story Title" and "author: The Author Name" - so you'd have to split on first :.)

Could also make use of this information in Inky in order to set the <h1> title of the story and the web page's <title> tag, and this provides a place for the metadata that could be used by an app like Lectrote.

I also wrote some quick instructions for how to grab the global tags out of an ink's json if you don't want to fully load up the story. This might be of dubious usefulness, but anyway:


To get global tags from a story's JSON without actually loading the full story:

  1. Let's assume storyObj is the story's JSON in JavaScript object form.

  2. Optional: Look at storyObj.inkVersion - it must be 14 or newer to use tags at all.

  3. var mainContentWeave = storyObj.root[0];, and check the result exists

  4. Iterate through the objects of mainContentWeave, stopping when you find one that isn't a tag:

    var globalTags = [];
    for(var i=0; i<mainContentWeave.length; i++) {
        var contentObj = mainContentWeave[i];
        if( !contentObj["#"] ) break;
        globalTags.push(contentObj["#"]);
    }
    
  5. globalTags is now a list of strings for the global tags in your ink story.

@erkyrath
Copy link

I'll be happy to add title-snarfing support to Lectrote when this is released in inkjs.

A method on Story that returns the global tags (as described in previous comment) would be ideal.

@y-lohse
Copy link
Contributor

y-lohse commented Oct 3, 2016

@joethephish I'm done porting the changes! Would you mind providing me with an ink file that has some tags in it so I can see if it works as expected?

@joethephish
Copy link
Member Author

Nice! Maybe take a look at the tags unit test?

@y-lohse
Copy link
Contributor

y-lohse commented Oct 3, 2016

That looks good but I need the compiled version of the ink, and I don't believe the public versions of inklecate can do this at the moment?

@joethephish
Copy link
Member Author

Good point! There's a branch, but it's easy for me to just give it to you, so here you go:

{"inkVersion":14,"root":[[{"#":"author: Joe"},{"#":"title: My Great Story"},"^This is the content","\n",null],"done",{"knot":[[{"#":"knot tag"},"^Knot content","\n",{"#":"end of knot tag"},"end",null],{"stitch":[[{"#":"stitch tag"},"^Stitch content","\n",{"#":"this tag is below some content so isn't included in the static tags for the stitch"},"end",null],{"#f":3}],"#f":3}],"global decl":["ev",2,{"VAR=":"x"},"/ev","end",null],"#f":3}]}

Which was compiled from:

VAR x = 2 
# author: Joe
# title: My Great Story
This is the content

== knot ==
# knot tag
Knot content
# end of knot tag
-> END

= stitch
# stitch tag
Stitch content
# this tag is below some content so isn't included in the static tags for the stitch
-> END

@y-lohse
Copy link
Contributor

y-lohse commented Oct 3, 2016

Perfect, thanks! I'll play with that.

@y-lohse
Copy link
Contributor

y-lohse commented Oct 4, 2016

Everything seems to work as expected on my end, so we can go ahead with the release whenever you want :-)

@joethephish
Copy link
Member Author

Okay, 0.6.0 is out now. Will release new version of inky when inkjs is ready :) (phew!)

@y-lohse
Copy link
Contributor

y-lohse commented Oct 7, 2016

There it is!

@joethephish
Copy link
Member Author

Hmm, I'm getting an error when running a hello world program: "[] is not a constructor", from this block of code, see the bit where it does new []():

        key: 'currentTags',
        get: function get() {
            var tags = new []();

            this._outputStream.forEach(function (outputObj) {
                //          var tag = outputObj as Tag;
                var tag = outputObj;
                if (tag instanceof Tag) {
                    tags.push(tag.text);
                }
            });

            return tags;
        }

This is when running in Chrome, so I guess it's not because it's a modern JS feature?

@y-lohse
Copy link
Contributor

y-lohse commented Oct 7, 2016

Ehm... where did you find that code block? 😕 I just checked the source from release 1.3.0 again and can't find it..?

@joethephish
Copy link
Member Author

Oh, odd! I'm pulling 1.3.0 from npm, and ink.js does indeed seem to be different from the version on github, and it tells me the modification date is 3rd October... (I'm an npm noob, not sure if I'm doing something wrong? I did npm update inkjs, and even tried deleting it and reinstalling it...)

@y-lohse
Copy link
Contributor

y-lohse commented Oct 8, 2016

Aaah, I see. If you update to 1.3.1, everything should be ok.

Longer version: inkjs has these 3 files (ink, ink.min and ink-es2015) but only the es-2015 is useful for node.js, so my intention was to only publish that one via npm — and I thought I was. But it turns out npm publishes whatever it finds inside my local dist folder, and this time it had some outdated ink and ink.min files there.
Since it can be useful to get the two other files via npm (for inky or lectrote or whatever), I've now changed the setup so that they also get properly recompiled and published to npm, so it shouldn't happen again.

@joethephish
Copy link
Member Author

Wonderful! That works great. Next Inky release incoming :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants