New ChoiceScript features for programmers

In the latest version of ChoiceScript, new features are available for programmers. They’re documented in the Advanced ChoiceScript section of the documentation, which means that you probably don’t need to use them. (I like to discourage people from getting too fancy in ChoiceScript games, because adding complexity rarely adds to the fun of the game.)

  1. Curly braces now replace *setref and *gotoref. For years, you’ve been able to use curly braces {} around variable names to return values by reference, like this:

     *create honesty 30
     *set virtue "honesty"
     *set score {virtue}
     Your ${virtue} score is now ${score}.

    But the curly braces would only work on the right-hand side of the *set statement. Now you can use it on the left-hand side of *set, eliminating the need for a separate *setref command.

     *set {virtue} %+ 20

    Similarly, you can use it in a *goto statement, eliminating the need for *gotoref.

     *create superpower "invisibility"
     Your super power is:
     *goto {superpower}
     *label invisibility

    *setref and *gotoref still work, but they’re now deprecated. We recommend using curly braces instead.

  2. Curly braces now work in more commands. The new curly syntax now works in a bunch of commands, including *rand, *input_text, and *input_number, but most importantly, it works in *goto_scene, and *gosub_scene.

     *goto_scene {doomsday} ending
     *goto_scene checkpoint {character_class}
     *goto_scene {previous} {favorite}
     *gosub_scene {previous} {favorite}

    EDIT: Beware that Quicktest effectively skips *goto_scene and *gosub_scene lines that use {} or []. (Randomtest works fine.) If you have a file with a section that can only be reached using a curly referenced *goto_scene command, consider adding a section like this somewhere in your game, enumerating the possible destinations.

     *if false
       *gosub_scene checkpoint chap1
       *gosub_scene checkpoint chap2
       *gosub_scene checkpoint chap3

    Quicktest will “run” those lines to verify that the chap1, chap2, and chap3 labels exist in the checkpoint scene, and verify that those labels will be actually covered by Quicktest.

  3. ${} now accepts arbitrary expressions. Previously, you could only use a simple variable name, like ${foo}, but now you can put any valid ChoiceScript expression in there, like ${gold+10} or even use curly braces for indirect references. ${{best_stat}}

  4. Experimental support for arrays. I don’t want anybody to rely on this syntax yet, because I may need to change it yet, but I would like feedback on it. (In other words, talk to us before submitting a HG or CoG game that uses arrays.)

    Arrays work by putting square brackets [] after the name of a variable, like this: foo[1]. It’s effectively the same thing as {"foo_"&1}. But you can put anything in the brackets, including variables, like this: strength[current_opponent].

    Arrays work anywhere curly braces work:

     *create current_opponent 1
     *create strength_1 50
     *create damage_1 20
     You did ${damage[current_opponent]} points of damage.
     *set strength[current_opponent] -damage[current_opponent]
     *goto dialog[current_opponent]
     *gosub_scene dialog[current_opponent] took_damage

I fully expect that there are bugs in the new code, so be sure to let me know of what you find. :smile:


Do array indices initialize from 0 or 1?

Wait, these aren’t the kind of arrays I’m used to. So, I’m not explicitly *creating an array, I’m *creating variables that can be referred to in an array-like fashion? Strength_1 is element 1 of array ‘strength’?

*create strength_1 5
*create strength_2 10

{strength[1]} prints 5 and {strength[2]} prints 10, ${strength[3]} gives a ‘variable does not exist’ error. Is this correct?

Yup, you’ve got it exactly. The indices don’t even have to be numeric.

I’d say that arrays don’t “count” from 0 or 1, because the system has no notion of the “length” of an array.

This could be an interesting thing to use, but to show us and then say not to use it is like waving a cookie in our face, then eating it.

To be clear, you can use the new curly stuff right away. We’ll use arrays ourselves in at least one project. If it works great and nobody had any significant objections, we’ll make arrays official.

By which I mean to say NOM NOM NOM NOM NOM NOM NOM NOM NOM. :wink:


Question, if I may, but what exactly does doing this:

*create current_opponent 1
*create strength_1 50
*create damage_1 20
You did ${damage[current_opponent]} points of damage.

Do? Does it tag opponent 1 with the damage? Or does it create an arithmetical equation subtracting “1” from whatever damage you inflicted?

The bit you quoted just creates the variables and prints one of them out. I assume you were really asking about this line:

*set strength[current_opponent] -damage[current_opponent]

That means the same thing as:

*set {"strength_"&current_opponent} -{"damage_"&current_opponent}

Since in this example current_opponent is 1, ChoiceScript will convert the curly braces to ordinary variables, like this:

*set strength_1 -damage_1

When the right-hand side of a *set starts with an operator like the minus sign, that means the same thing as:

*set strength_1 (strength_1 - damage_1)

In this case, strength_1 is 50 and damage_1 is 20, so it would set strength_1 to 30.

So, if I’m understanding correctly, the [current_opponent] added to the end of a variable (since current opponent was created with a value of 1) it would reference all stats of current opponent (or at least all stats with _1). And it would no longer do so were current opponent set to 2 in this example?

Yeah, the way to think of it is [current_opponent] means _1 when current_opponent is 1, or _2 if current_opponent is 2.

Here’s a slightly more realistic example of how you could use it.

*set damage 20
*set current_opponent 1
*gosub damage_opponent
*set current_opponent 2
*gosub damage_opponent

... blah blah blah ...

*label damage_opponent
*set health[current_opponent] -damage
*if (health[current_opponent] < 0)
  ${name[current_opponent]} is dead.

Does the latest version of CS support *script modding and CSS mods?

Those are policy decisions, not technical issues. Obviously *script and CSS mods work today. But running arbitrary *script on is a security risk, so it’s very unlikely that we’ll ever allow that in games we host.

As for CSS mods, I guess we could? But that gets back to the question of what our games should look like. If we do a fancy UI redesign, would we stick with whatever custom CSS you provide? Or would we blow it away and replace it with our stuff?

Regarding the use of *script on the official COG site: Wouldn’t running the games on its own “dummy” site, on their own shared hosting plan and then adding it to the COG site in an iframe solve that issue?

That’s what sites do that want to host untrusted content, but for CoG games, we do trust/evaluate the content, and we’d like our main domain to receive full credit in Google for the game.

I see, makes sense, since it adds more pages, thereby authority and keyword density, not to mention you have the social buttons in the game there for social media backlinks… smart. Actually, if I may ask one last question, how would *script compromise security if its in it’s own folder? If I remember right *script simply calls a function into being from the index file right? So how would it do that? Via someone tampering with the code in a browsers’ dev console? Or would it reveal game file paths?

It’s a XSS attack. Basically, *script can do anything at all to the page, including sending your authentication cookies to a third party, prompting the user to enter their credit card information, tricking users into giving away passwords, etc.

The most severe XSS attacks involve disclosure of the user’s session cookie, allowing an attacker to hijack the user’s session and take over the account. Other damaging attacks include the disclosure of end user files, installation of Trojan horse programs, redirect the user to some other page or site, or modify presentation of content.

So, it would be an attack from the inside of the program itself much like adware on a computer? Couldn’t you (theoretically without thinking about work time it would take) check and fleece stuff like that from COG’s end?

I can’t write code that looks for “evil” *script. I can manually review *script commands, but that’s painful and time consuming. I guess I could whitelist some known-good *script, but normally I do that by adding features to ChoiceScript, converting *script to non-*script code.


I just finished reading this post, so I haven’t had the time to test any of the new features, but as a programmer, I am really excited for these. I’m planning on starting a new project soon, so I’ll likely dive into these and if I run into any issues/suggestions, I’ll forward them here!

Using curly braces has worked well but seems to be picked up as an error in the current Quicktest. If I run my game, curly braces work, but Quicktest gives this error:

ERROR: couldn’t open web/mygame/scenes/{hold_scene}.txt

Here’s the code

*create hold_scene ""

*set hold_scene "temp-chapter"

    *goto_scene {hold_scene}
  #Test Save
    *goto TestSS
1 Like

Uh, oh, you’re right. I’ll get to work on fixing it.