klembot / chapbook

A simple, readable story format for Twine 2.
https://klembot.github.io/chapbook
MIT License
80 stars 24 forks source link

[Request] Ability to Set a Variable via clicking a link, & Setting variables mid-passage #90

Open Encyclopedus opened 3 years ago

Encyclopedus commented 3 years ago

Setting variables via link At present, the Chapbook guide does not provide any method of setting variables via clicking a link (referred to as "Setter links" in Twine 1.0+). According to the Twine wiki, this is normally accomplished as follows: [[displayed text|title of passage][$variable = expression]]

As a result, the only clear way to set a variable based on having pressed a specific link is to adjust it on the destination page via the var section. A consequence of this is that you cannot update a variable via a link when linking back to the same passage.

For most Twines this isn't a huge obstacle, but if your story involves the intentional repetition of passages (such as a story about a time-loop), having a single passage with conditional behavior is much more efficient than creating duplicate pages which embed the same information with minor changes.

Please consider adding the ability to set a variable via clicking a link.

Setting variables mid-passage For reasons similar to the above, it would be awesome if we could set or update a variable as the result of an if/else/unless statement without having to force the player to click through to a new passage. i.e.

[if TimeLoop > 1 && TimeLoop < 3]
Something here seems familiar...
[set RoomLink: RoomDescription + TimeLoop]
[set KnifeDodged: true]
{link to: RoomLink, label: 'Open the door.'}

Thanks!

klembot commented 3 years ago

I have an idea related to this that I haven't implemented yet, of "live inputs". The idea being you can enter text into fields, click buttons or links in a passage, and it doesn't navigate you anywhere, but instead re-renders the passage based on the new state. The canonical example I'm thinking of is a passage where you can try to unlock a combination lock by entering numbers without having to click a link after setting each one.

Encyclopedus commented 3 years ago

Interesting. That would definitely be useful! Was just talking to a friend about incorporating a chess board into a puzzle, and that could simplify things.

Encyclopedus commented 3 years ago

On a separate note, it would be cool to have the ability to "hide" text after a set time, such as in the case of a trap room where the ceiling is closing in. I.e.

As you enter the room, you hear a soft click and the ceiling begins to close in on you.
[after 2500ms]
The ceiling inches closer. In a matter of minutes, you'll be crushed to death.
[after 6000ms]
Only a few feet remain between your head and the ceiling.
[continue]
[hide after 15000ms]
[[Open the door to the north]]
[[Open the door to the south]]
[continue]
[after 15000ms]
You crouch down to avoid being crushed and discover both the doors to the north and south are now blocked
by the rapidly-descending ceiling. However, in the dim light of the room you suddenly notice a hole
 in the ceiling.
[[Climb through the hole in the ceiling.]]

So the options "open the door to the north/south" would both be available for 15sec while new descriptive text is appearing about the ceiling coming down. Then suddenly at 15sec, the options disappear... but a new options appears.

Currently in order to accomplish this, you'd have to force the player to another page i.e. ([[Search for exits]]) and then display possible exits based on how long it took them to press the link, which doesn't have the same narrative impact.

klembot commented 3 years ago

I hesitate at adding functionality to hide text after a delay because of accessibility reasons. People with cognitive disabilities may need longer to read text, and if things are on a really short timer (e.g. a QTE), then that can pose a problem for people with certain physical disabilities.

Of course, an author can allow players to skip over these things or give them an alternate way to proceed, but as a format author, I can't force them to do that. If we could add something at the format level to always give players an out, then I'd probably be good with it... but I suspect authors would be unhappy with that, because they'd be concerned about players without disabilities 'cheating'. (Which is a whole other discussion...)

Anyway, this delayed disappearing stuff probably merits a separate issue.

gustavus-cul commented 2 years ago

You live inputs idea would be a helpful one, I'm trying to do a language switching on click at anytime, and currently I don't see another way except linking to a separate identical passage, except in the other language.

themizarkshow commented 1 year ago

Is this still on the table? It would simplify my life a ton for a couple projects (love using Chapbook but this would make it so much more powerful)

hituro commented 11 months ago

Is this still on the table? It would simplify my life a ton for a couple projects (love using Chapbook but this would make it so much more powerful)

The following custom insert creates links that set values when clicked

engine.extend('1.0.0', () => {

    engine.event.prependListener('dom-click', el => {
        let vars    = el.dataset.cbSetVars;
        if (vars) {
          let result = engine.render(vars.replace(';',"\n")+"\n--");
        }
    });

    config.template.inserts = [{
        match: /^set\s+link/i,
        render(passage, props) {
          if (passage) {
            return `<a href='javascript:void(0)'
                       data-cb-go="${passage}"
                       data-cb-set-vars="${props.set}">${props.label}</a>`;
          }
        }
    }, ...config.template.inserts];
});

To use

{set link: 'Another passage', label: 'A link', set: 'foo: 2;bar: 10'}
aliceh75 commented 2 months ago

Replying to this requirement:

For reasons similar to the above, it would be awesome if we could set or update a variable as the result of an if/else/unless statement without having to force the player to click through to a new passage.

As a simple approach, you can use the [Javascript] modifier within if statement to change a variable value. The following will display "mood: surprised" on first visit, "mood: happy" on second visit and "mood: bored" subsequently.

mood: "surprised"
--
[if passage.visits == 2]
[Javascript]
engine.state.set('mood', "happy");
[if passage.visits > 2]
[Javascript]
engine.state.set('mood', "bored");
[continue]

mood: {mood}

Building on from that, if you'd rather have a simpler syntax that doesn't involve the [Javascript] modifier, you can add the following to your story Javascript (note: you can't define this and use it within the same passage - add it to the story Javascript):

engine.extend('2.0.0', () => {
    engine.template.inserts.add({
        match: /^set-var/i,
        render(firstArg, props, invocation) {
            engine.state.set(firstArg, props.value);
            return "";
        }
    });
});

And then you can use it directly. The following passaged named "test javascript if" will first display "we have hello", then on second visit "we have hello world" and then "we have hello universe" on subsequent visits:

someValue: "hello"
--

[if passage.visits == 2]
{set-var: "someValue", value: someValue + " world"}
[if passage.visits > 2]
{set-var: "someValue", value: someValue + " universe"}
[continue]

we have {someValue}

[[Next->test javascript in if]]

There is no guarantee however that this will work in future versions of Chapbook (as it relies on side-effects being allowed inside inserts) - it would be better to have the functionality build in !

aliceh75 commented 2 months ago

What I think would be more in the spirit of Chapbook would be a vars section at the beginning of if statements:

[if someCondition ]
someValue: someValue + 1
--
New value is {someValue}
[continue]