inkle / ink

inkle's open source scripting language for writing interactive narrative.
http://www.inklestudios.com/ink
MIT License
4.07k stars 489 forks source link

Is there a C# API to iterate over all the content while ignoring choices? #836

Open kodra-dev opened 1 year ago

kodra-dev commented 1 year ago

I have a custom "sub-format" for my content.

John: Hello!

I'll parse this John and check if John is a character in my game, so if I made a typo:

Jonh: Hello!

I can show an error.

I already know how to do the above, with a caveat: the error only shows up when the player actually runs into Jonh: Hello!. If I have an ink script like this:

+ [hello]
Jonh: Hello!
+ [good night]
John: Good night.

and the player chooses [good night], then it doesn't show errors. I'd like to make an editor button that catches these kinds of errors beforehand, before the player even plays the script.

How could I do this? I think the simplest way is just to iterate over all the content and validate them, but I don't know how to do it with the API.

machinex7 commented 1 year ago

I had a similar situation like this, but I couldn't find a solution using the API. My solution was I wrote a very basic reader for the raw ink files and I would run my processor over it right before I loaded the actual compiled JSON file. That way I could just find each line that matched my format or tag, validate it, and output a line number if there was a problem.

skalogryz commented 9 months ago

Yes, this is very possible using InkParser class. (which is part of the Ink Compiler project)

You create InkParser object passing the json content. Then call method Parse(). As a result you'll have the story class, which is the root Ink.Parsed.Object.

The primary interest is text property. Which needs to be parsed out for the name prefix. But you only need to parse it if the object is of Ink.Parsed.Text class.

Each Ink.Parsed.Object might have some "content" (which contents the sub-objects) and allows to go "deeper" into the story.

Here's the example of the code:

    static void WalkObj(Ink.Parsed.Object obj, string pfx = "")
    {
        if (obj == null) return;
        if (obj is Text t)
        {
            Console.Write(pfx);
            Console.Write(obj.GetType().Name);
            Console.Write(" \"{0}\"", t.text);
            if (t.hasOwnDebugMetadata)
            {
                Console.Write(" (");
                Console.Write(t.debugMetadata.fileName);
                Console.Write(":");
                Console.Write(t.debugMetadata.startLineNumber);
                Console.Write(") *");
            }
        }
        else
        {
            Console.Write(pfx);
            Console.Write(obj.GetType().Name);

            if (obj is Ink.Parsed.Choice ch)
            {
                Console.WriteLine();
                Console.Write(pfx);
                Console.WriteLine(" has start:  {0}", (ch.startContent != null));
                Console.Write(pfx);
                Console.WriteLine(" has inner:  {0}", (ch.innerContent != null));
                Console.Write(pfx);
                Console.WriteLine(" has choice: {0}", (ch.choiceOnlyContent != null));
            }
        }

        Console.WriteLine();
        if (obj.content != null)
        {
            foreach (Ink.Parsed.Object sub in obj.content)
                WalkObj(sub, pfx + "  ");
        }
    }

Here's how the code can be used

string js = File.ReadAllText(inputFn);
InkParser p = new InkParser(js, inputFn);
Ink.Parsed.Story flow = p.Parse();
WalkObj(flow);