YarnSpinnerTool / YarnSpinner

Yarn Spinner is a tool for building interactive dialogue in games!
https://yarnspinner.dev
MIT License
2.3k stars 201 forks source link

Proposal: "sticky" dialogues #346

Closed st-pasha closed 7 months ago

st-pasha commented 1 year ago

Introduction

Add the possibility to have looping dialogues, as common in many games. Such dialogue behaves similar to the regular dialogue, except that when the execution flow tries to leave the dialogue, the dialogue just repeats itself.

Rationale

This is used as a primary dialogue mode in many games, the idea being that normally you wouldn't leave the conversation with an NPC unless explicitly directed to do so. Consider this small dialogue as an example:

title: InnkeeperDialogue1
---
Innkeeper: Hello traveler! Need a place to rest, or a drink to ease your sorrows?
-> I'd like to rent a room
   ...
-> A drink please
   ...
-> What can you tell me about the bandits nearby? <<if onQuest("Bandits")>>
   ...
-> Heard any interesting rumors lately?
   ...
-> (show the sign of the Thieves)
   ...
-> END
===

The way this dialogue is supposed to proceed is that when the player asks about something, then after the innkeeper's answer the dialogue flow goes back to the main choice selection, and the player can ask about something else.

Proposed solution

The proposed solution is to introduce a new dialogue kind, designated by the "fat" arrows =>. This dialogue would need to be exited explicitly, via the command <<break>> (or also <<stop>>):

title: InnkeeperDialogue1
---
Innkeeper: Hello traveler! Need a place to rest, or a drink to ease your sorrows?
=> I'd like to rent a room
   ...
=> A drink please
   ...
=> What can you tell me about the bandits nearby? <<if onQuest("Bandits")>>
   ...
=> Heard any interesting rumors lately?
   ...
=> (show the sign of the Thieves)
   ...
=> END
   <<break>>
===

Detailed design

The arrow => needs to be added to the YarnSpinner grammar, which will be recognized at the same place where the regular arrow -> is recognized. When such arrow is used, the option will be marked as "sticky" internally.

A set of "sticky" options will form a "sticky" OptionSet. It should be a compile-time error to mix sticky and non-sticky options (i.e. have both -> and => arrows intermix).

When this kind of OptionSet is executed by the DialogueRunner, it should effectively place it inside an infinite loop: whenever the control loop exits the OptionSet normally, the OptionSet should be executed again (including re-evaluating which options are enabled).

The new <<break>> command will function to exit this loop. If several dialogue loops are nested, then <<break>> exits only the innermost one.

Backwards Compatibility

The impact to the existing code should be minimal. The only users affected will be those who have dialogue lines starting with =>, which shouldn't be very common. The upgrade script may search for such lines and prepend them with \:

\=> Enter here <=

This will require that YarnSpinner recognized \= escape sequence (and also \- while we're at it).

Alternatives considered

  1. Adding a special command <<while true>> ... <</endwhile>>:
    <<while true>>
     -> I'd like to rent a room
     -> A drink please
     -> Any rumors?
    <<endwhile>>
  2. Adding the block-command <<dialogue>> ... <</enddialogue>>
    <<dialogue>>
     -> I'd like to rent a room
     -> A drink please
     -> Any rumors?
    <<enddialogue>>

Both of these approaches seem to be more verbose and less writer-friendly than the suggested approach with => arrows.

  1. Don't add new syntax, as the same functionality can be achieved by creating a new node which would jump to itself at the end of the loop. The <<visit>> command (#303) will also be needed in order to make inner dialogue loops work.

    This approach is even more cumbersome than 1 or 2. The games that use dialogue loops (e.g. Fallout, Skyrim, Gothic) use them for every single NPC dialogue, so having to create extra nodes would be a big inconvenience for them.

Acknowledgments

McJones commented 1 year ago

Thanks for the detailed proposal, I have a quick question about the approach. In particular what does this give us over say writing the following yarn instead of creating new syntax?

title: InnkeeperDialogue1
---
Innkeeper: Hello traveler! Need a place to rest, or a drink to ease your sorrows?
-> I'd like to rent a room
    ...
-> A drink please
    ...
-> What can you tell me about the bandits nearby? <<if onQuest("Bandits")>>
    ...
-> Heard any interesting rumors lately?
    ...
-> (show the sign of the Thieves)
    ...
-> END
   <<stop>> // could also be a jump to some hub node

<<jump InnkeeperDialogue1>>
===

Just because this is functionally the same thing and roughly the same amount of text for the writer to write, and works right now. Is there some use case I am missing?

st-pasha commented 1 year ago

What does this give us over say writing the following yarn instead of creating new syntax?

title: InnkeeperDialogue1
---
Innkeeper: Hello traveler! Need a place to rest, or a drink to ease your sorrows?
-> I'd like to rent a room
    ...
-> A drink please
    ...
-> What can you tell me about the bandits nearby? <<if onQuest("Bandits")>>
    ...
-> Heard any interesting rumors lately?
    ...
-> (show the sign of the Thieves)
    ...
-> END
   <<stop>> // could also be a jump to some hub node

<<jump InnkeeperDialogue1>>
===

In this particular case, the innkeeper will keep repeating the "Hello ..." line every time, which is undesirable. The workaround is to split the node into two, one for the greeting and another for the dialogue loop itself. But this is a bit of a hassle: instead of modeling the nodes as is most natural from the writer's perspective, they'd be forced to set up artificial nodes for the purpose of manually creating a loop.

Another thing to look out is nested dialogue loops. These don't happen in every dialogue, but occasionally they do. For example:

=> Tell me about...
   => Innos
   => Adanos
   => Beliar
   => the orcs
   => the dragons
   => (back)
      <<break>>

In this case "tell me about..." section is an inner loop, and the player can keep asking about different entities, until they decide to break out into the outer loop.

Implementing this without the "sticky dialogue" feature would require setting up yet another node, and then "break" command would need to jump back to the main dialogue loop.

So, it's all doable, but cumbersome.

mmoderau commented 1 year ago

In this particular case, the innkeeper will keep repeating the "Hello ..." line every time, which is undesirable.

To be fair, so would he in the original proposal, unless the sticky option set would not display the original prompt.

Usually such looping dialogue has a follow up prompt, like "Anything more you need, m'lord?"

McJones commented 7 months ago

Thanks for the proposal, you may have seen the recent Yarn Spinner 3 expectations announcement that there are multiple sections on the upcoming line groups and storylets. These are inspired (amongst other sources) based on the discussion in this proposal and allow for this structure to be recreated, albeit in a different manner.

I am closing this issue now as specifics around the structure of storylets and line groups should be in it's own proposal. Thanks so much for the excellent proposal.