ink icon indicating copy to clipboard operation
ink copied to clipboard

What is the correct way of "toggling" items in an ink list from C#?

Open tomkail opened this issue 2 years ago • 1 comments

We've got an ink list that looks like this: LIST valuesList = Family, Happiness, Love

The docs show how to create a new ink list from C#, but not how to manipulate an existing one. What is the correct way of toggling the items in that list from C#?

Our attempts to get this working have worked, except that for some reason the list isn't saved when exporting story state to json. We're assuming that the reason is that we're referencing the wrong instance of list items, or something.

What we're doing is:

  1. Getting the full list of InkListItem from the InkList.origins
  2. Getting the InkList from variableState["valuesList"]
  3. Using AddItem/Remove to toggle the items in the list.

Is this the correct way to do this?

tomkail avatar Jul 18 '22 10:07 tomkail

Are you settingvariableState["valuesList"] to be your newly modified InkListItem at the end?

russellquinn avatar Jul 27 '22 16:07 russellquinn

I was! It turned out to be a bug, which was fixed. The only safe way to manipulate lists from code is to create a new list and write it to the old list - You should think of them as value types. They are, frankly, bloody awful to work with. It's probably easiest in many ways to do manipulation in ink if you can.

That said, here's some code that I use to manipulate an ink list that's shown to the user as a list of items that can be toggled on and off. It's annoyingly long!

// This clones the list.
var listVar = new InkList(originalList);
        listVar.Clear();
        foreach(var inkListItem in originalList.all) {
            if(originalList.ContainsKey(inkListItem.Key)) {
                listVar.Intersect(InkList.FromString(inkListItem.Key.fullName, story));
            }
        }

    // This adds and removes items that have been changed when the user clicks on the togglable buttons
        bool changed = false;
        foreach(var item in items) {
            if(item.isOn && !listVar.ContainsKey(item.inkListItem)) {
                listVar.Intersect(InkList.FromString(item.inkListItem.fullName, story));
                changed = true;
            }
            else if(!item.isOn && listVar.ContainsKey(item.inkListItem)) {
                listVar.Remove(item.inkListItem);
                changed = true;
            }
        }

// Then we write the new list to the ink!
        if(changed) 
            story.variablesState[listInstruction.listVariableName] = listVar;```

tomkail avatar Jan 30 '23 17:01 tomkail

Oh, right. I did come across that too. I have a StoryController C# class that wraps everything Ink related, so I have a layer of abstraction. My code for setting Ink Lists just works with a string[] array, so I'd do toggle manipulation elsewhere in my game code, then pass a string[] array to StoryController and let it handle writing it back to Ink:

STORYCONTROLLER

    public bool SetListValue(string variableName, string listType, string[] items)
    {
        if (variableName == null || story == null || listType == null || items == null)
        {
            return false;
        }

        InkList newList = new InkList(listType, story);

        foreach (string item in items)
        {
            newList.AddItem(item);
        }

        return SetVariableValue(variableName, newList);
    }

    public bool SetVariableValue(string variableName, object value)
    {
        if (variableName == null || story == null || value == null)
        {
            return false;
        }

        if (story.variablesState.Contains(variableName) == false)
        {
            Loggy.LogError("SetVariableValue: Cannot find variable: " + variableName + " in Ink script", LoggyType.InkScript);
            return false;
        }

        story.variablesState[variableName] = value;
        return true;
    }

russellquinn avatar Jan 30 '23 17:01 russellquinn

Oh boy would you be willing to share it? I'd love to see how others do techy ink stuff!

One thing for future readers - my code should deal with cases where a list contains list items from several difference lists. You may not want that! It's a thing to be aware of that ink lists can do, and it explains a lot of weirdness about the API!

tomkail avatar Jan 30 '23 17:01 tomkail

Yeah, I hope to at some point. It also abstracts Ink Flows completely, so different parts of the game can have a Flow object and perform functions on it, without caring about anything else in the system. Then StoryController handles all the Flow switching and management in Ink; it can turn Flows on and off dynamically depending on game contexts, etc. It's like a thread-management layer. (And the Ink Engine handles rapid Flow switching really well!) Whenever I finally finish the game I'm working on, I'll try and open source this part.

russellquinn avatar Jan 30 '23 18:01 russellquinn