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: Options that disappear and fallback. #364

Closed gefrituurdeaugurk closed 7 months ago

gefrituurdeaugurk commented 1 year ago

Introduction

A very common use-case in adventure games is selecting dialogue options that disappear after selection. Another one is automatically selecting an option when no remaining selectable options remain.

Rationale

Right now when writing dialogue in Yarn, it is mandatory to use an IF-statement with a dedicated declared Boolean variable for each dialogue option you only want to show once, as well as setting a Boolean variable when selection is chosen, to ensure it will never appear again. This is hard to read and not friendly for writers. Example issue:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! <<if $bossknows = false>>
 <<set $bossknows to true>>
-> Please? <<if $please = false>>
 <<set $please to true>>
-> Okay bye. 

Proposed solution

It would be great to have this use-case supported by default in language since this is such a common problem:

The previous example could be rewritten to:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! #once
-> Please? #once
-> Okay bye. #final

The above would remove each line after it’s selected because these lines contain the #once tag.

final would automatically be chosen once no more options are visible.

Detailed design

See above.

Backwards Compatibility

This would remain compatible with the previous version due to the use of tags.

Alternatives considered

The same could be accomplished by introducing a new tag (which would not be as backwards compatible):

-> I am a normal option -> Me too *> But I would only be selectable once.

> And I would be selected when I’m the only option.

L

McJones commented 1 year ago

I personally really like this idea. There are two pieces to this, only showing options once and having a fallback option. I will cover the once part first because it is much simpler and I think the stronger part of the idea.

Once off options

So the core issue is it is a bit of a pain having to write out the flags needed to toggle the availability of options:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! <<if $bossknows == false>>
    <<set $bossknows to true>>
-> Please? <<if $please == false>>
    <<set $please to true>>
-> Okay bye.

and has added issues of requiring you as the writer to come up and manage all the variables for handling these yourself. It would be nice to be able to just flag each line and not worry about it, essentially being able to do the above with the following:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! #once
-> Please? #once
-> Okay bye.

This could then be converted at compile time into the following:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt1 == false>>
    <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt1 = true>>
-> Please? <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt2 == false>>
    <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt2 = true>>
-> Okay bye.

This way you don't need handle variables yourself as we can generate them, although we'd do this not at the syntax level but when emitting bytecode.

Conditionals

The next issue is how to handle situations where you also have conditions on the options:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! <<if $know_boss == true>> #once
-> Please? #once
-> Okay bye.

We could make it that you aren't allowed to mix and match but I think it wouldn't be too much work to modify the output to be equivalent to the following:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt1 == false && $know_boss == true>>
    <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt1 = true>>
-> Please? <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt2 == false>>
    <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt2 = true>>
-> Okay bye.

Caveat

Now this doesn't technically stop your custom dialogue views still selecting the options that have been seen but that is on you, and the default option view won't allow that behaviour so I think it is fine. This also does require making and storing variables for each line but I don't think there is any better way that doesn't involve massive restructuring of how we handle options.

Final options

Now this I am less sold on as an idea as I think having it as a choice is pretty much always gonna be what people want, but I suppose you could do a final tag where it is only available once all once lines have been done. This would mean something like the following:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! #once
-> Please? #once
-> Okay bye. #final

would become:

Guard: You're not allowed in!
-> Sure I am! The boss knows me! <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt1 == false>>
    <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt1 = true>>
-> Please? <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt2 == false>>
    <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt2 = true>>
-> Okay bye. <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt1 == true && $Yarn.Internal.OptionShown.OptionGroup1.Opt2 == true>>

but as I said I am less sold on this vs the general once tag.

tldr

Totally down with adding a #once tag into options that is a sugar onto writing a more complex line conditionals. This can be done through rewriting lines at compile time. A #final tag could also be done in a similar manner, but I am less convinced of the utility of it.

gefrituurdeaugurk commented 1 year ago

Great to hear you like the idea!The reason why I incorporated #final (or #fallback) is that if no choices remains in case all of the choices are tagged #once, how could you control the flow? But I agree there is probably a better way :-)  On 24 May 2023, at 06:01, Tim Nugent @.***> wrote: I personally really like this idea. There are two pieces to this, only showing options once and having a fallback option. I will cover the once part first because it is much simpler and I think the stronger part of the idea. Once off options So the core issue is it is a bit of a pain having to write out the flags needed to toggle the availability of options: Guard: You're not allowed in! -> Sure I am! The boss knows me! <<if $bossknows == false>> <<set $bossknows to true>> -> Please? <<if $please == false>> <<set $please to true>> -> Okay bye.

and has added issues of requiring you as the writer to come up and manage all the variables for handling these yourself. It would be nice to be able to just flag each line and not worry about it, essentially being able to do the above with the following: Guard: You're not allowed in! -> Sure I am! The boss knows me! #once -> Please? #once -> Okay bye.

This could then be converted at compile time into the following: Guard: You're not allowed in! -> Sure I am! The boss knows me! <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt1 == false>> <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt1 = true>> -> Please? <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt2 == false>> <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt2 = true>> -> Okay bye.

This way you don't need handle variables yourself as we can generate them, although we'd do this not at the syntax level but when emitting bytecode. Conditionals The next issue is how to handle situations where you also have conditions on the options: Guard: You're not allowed in! -> Sure I am! The boss knows me! <<if $know_boss == true>> #once -> Please? #once -> Okay bye.

We could make it that you aren't allowed to mix and match but I think it wouldn't be too much work to modify the output to be equivalent to the following: Guard: You're not allowed in! -> Sure I am! The boss knows me! <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt1 == false && $know_boss == true>> <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt1 = true>> -> Please? <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt2 == false>> <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt2 = true>> -> Okay bye.

Caveat Now this doesn't technically stop your custom dialogue views still selecting the options that have been seen but that is on you, and the default option view won't allow that behaviour so I think it is fine. This also does require making and storing variables for each line but I don't think there is any better way that doesn't involve massive restructuring of how we handle options. Final options Now this I am less sold on as an idea as I think having it as a choice is pretty much always gonna be what people want, but I suppose you could do a final tag where it is only available once all once lines have been done. This would mean something like the following: Guard: You're not allowed in! -> Sure I am! The boss knows me! #once -> Please? #once -> Okay bye. #final

would become: Guard: You're not allowed in! -> Sure I am! The boss knows me! <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt1 == false>> <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt1 = true>> -> Please? <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt2 == false>> <<set $Yarn.Internal.OptionShown.OptionGroup1.Opt2 = true>> -> Okay bye. <<if $Yarn.Internal.OptionShown.OptionGroup1.Opt1 == true && $Yarn.Internal.OptionShown.OptionGroup1.Opt2 == true>>

but as I said I am less sold on this vs the general once tag. tldr Totally down with adding a #once tag into options that is a sugar onto writing a more complex line conditionals. This can be done through rewriting lines at compile time. A #final tag could also be done in a similar manner, but I am less convinced of the utility of it.

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: @.***>

st-pasha commented 1 year ago

A bit unclear whether #once would work within a single dialogue iteration, or as a global state. For example, if you leave the dialogue and come back -- can you still claim that "The boss knows me!" ?

McJones commented 1 year ago

umm so I see it as being a global state, it would persist over multiple invocations of the dialogue options.

gefrituurdeaugurk commented 1 year ago

I would see this a global state. It’s how adventure games often work.This global state is similar to how Ink works, however, Ink doesn’t assume the ‘once’ behavior as optional but uses the symbol to designate an option that goes away, and + to stick.E.g:What would you like?  I want some pizza * I’ll have the crab soup + Let’s talk about the murder case. On 24 May 2023, at 23:30, Pasha Stetsenko @.***> wrote: A bit unclear whether #once would work within a single dialogue iteration, or as a global state. For example, if you leave the dialogue and come back -- can you still claim that "The boss knows me!" ?

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: @.***>

desplesda commented 7 months ago

The once feature [has been announced as part of Yarn Spinner 3.0]. As a result, I'll close this thread here. Thanks for the discussion in this thread, everyone!