ephread / inkgd

Implementation of inkle's Ink in pure GDScript for Godot, with editor support.
MIT License
305 stars 33 forks source link

Feature: refresh current path #73

Open mttkay opened 1 year ago

mttkay commented 1 year ago

In most games, game state that is not handled by Ink itself can change after an ink knot has already been rendered.

This can render Ink's story state stale.

For example, imagine I can pick up an item on the story scene told by the current knot. This could happen via an external function call from Ink into your game. However, if one of the choices in the current knot is itself dependent on the player owning that particular item (e.g. via conditional diverts), the current scene state is now stale and needs to be refreshed.

I have not found a clean way to do that with inkgd. The primary issue is that inkgd forgets the current path as soon as it has finished playing the current knot (i.e. it current_path always returns null so you cannot remember it). I have to work around this by capturing this state manually in a data structure StorySegment by capturing internal inkgd state such as path pointers before allowing the story to continue:

class StorySegment:
    var text: String
    var type: int  # SegmentType
    var path: String
    var visit_count: int
    var choices: Array

    func _init(ink_player):
        type = ink_player.get_variable("segment_type")
        path = _path_from_player(ink_player)

    func _path_from_player(ink_player) -> String:
        var story_state = ink_player._story.state
        var ink_path = story_state.current_pointer.path
        if not ink_path:
            ink_path = story_state.previous_pointer.path
        assert(ink_path, "no ink path found")

        var primary = ink_path.get_head().name
        var secondary = ink_path.get_tail().get_head().name
        return "%s.%s" % [primary, secondary] if secondary else primary

I use the same mechanism to provide a scene refresh function, which "rewinds" inkgd's state to the current knot by replaying the captured pathname onto InkPlayer#choose_path and then call InkPlayer#continue_story:

func refresh():
    _ink_player.choose_path(_current_segment.path)
    _ink_player.continue_story()

However, this is clearly a workaround, and it has some unintended side effects such as incrementing the visit count, when the player really only visited this scene once.

I think it would be helpful to: