Les narramiettes de ink + Calico

Pour un projet en cours, j’aimerais utiliser un système de naramiettes avec ink + Calico. Calico en propose déjà un, mais il a apparemment quelques soucis et CrocMiam a créé une version améliorée, donc je crée un sujet pour en parler.

2 « J'aime »

Le fait d’en discuter hier m’a relancé sur le projet, j’ai pas mal revu le truc, je cherche encore la syntaxe la plus sympa pour pouvoir faire un max de choses (tenant compte des limitations d’ink) et je prépare une démo.
Mais en gros, on peut déclarer les storylets à peu près comme avec le plugin de calico, le JS les détecte et expose des fonctions pour les sélectionner selon des critères et les afficher.

Ah cool !
Mes compétences en ink sont vraiment basiques pour le moment, celles en JS encore plus. A priori, je n’ai pas besoin de trucs très compliqués, juste ouvrir/fermer des narramiettes suivant des critères logiques, en tirer une à la fois, et les hiérarchiser un peu, idéalement de manière flexible, pour par exemple éviter de tirer la même narramiettes plusieurs tours de suite s’il y en a d’autres qui sont disponibles.
En théorie, ça avait l’air possible avec le système de Calico, mais je n’ai pas testé assez pour voir si ça fonctionnait bien comme je pensais.
Quand tu auras un système dont tu es satisfait je voudrais bien tester.

L’idée, c’est qu’il n’y a pas besoin de toucher au JS pour l’auteur, donc pas d’inquiétude de ce côté là.
Pour les fonctionnalités demandées :

  • ouvrir/fermer des narramiettes : en définissant le stitch =open, on contrôle ça
  • en tirer une à la fois : on peut choisir combien on en veut
  • les hiérarchiser un peu : il y a =exclusivity pour ne garder que les plus exclusives et =urgency pour les trier
  • idéalement de manière flexible :
    • on peut mettre ce qu’on veut comme expressions dans les stitches {true}, {alfred_home.kitchen.visited}, {health <= 5}, {func()}
    • on peut ajouter des stitches custom si besoin
    • on peut filtrer arbitrairement avec une fonction ink qui a accès aux valeurs évaluées des stitches

Ce qu’il manque :

  • tirage aléatoire
  • syntaxe définitive pour la sélection des storylets

Avec Calico, ça prend une storylet ouverte au hasard parmi les plus urgentes et exclusives de la catégorie demandée et basta. Ça peut suffire. Tu ne peux pas en avoir plusieurs, et c’est forcément aléatoire. Donc pas hyper souple.

J’avoue que ça m’arrange un peu que ce soit aléatoire je pense. Il faut encore que je teste des trucs, pour le moment, j’ai trop peu de narramiettes pour que mes tests soient concluants.

1 « J'aime »

Je viens justement d’ajouter de l’aléatoire, soit uniforme, soit avec des poids différents pour chaque storylet (je crois que c’est la frequency de Dendry).

Format storylet

Je reprends à peu près le format de Calico :

=== nom_storylet
#storylet
= open
{true}
= exclusivity
{0}
= urgency
{0}
= frequency // N'existe pas dans Calico
{1}
= content // S'appelle text dans Calico
// Contenu de la narramiette, a priori un choix qui permet de la lancer mais vous êtes libre de mettre plusieurs choix
+ [Quête du talisman sacré] 
…
->->

Les éléments obligatoires :

  • le knot qui englobe toute la storylet
  • le tag #storylet
  • le stitch =content, qui doit être un tunnel, c’est à dire qu’il se termine systématiquement par un ->-> (ou un -> END pour une fin de jeu)

Ce qui est optionnel :

  • les catégories sur le tag: #storylet: quest, #storylet: quest, phase_1
  • les stitches :
    • =open (défaut = true) : sert à savoir si un storylet est disponible ou non
    • =exclusivity (défaut = 0) : sert à exclure toute storylet d’un niveau inférieur (s’il y a 3 storylets d’exclusitivity 1 et une d’exclusitivity 2, seule cette dernière sera disponible)
    • =urgency (défaut = 0)
    • =frequency (défaut = 1)

Vous pouvez mettre ce que vous voulez dans ces stitches à condition qu’ils terminent par une valeur entre accolades : {valeur}. Ça peut être une simple valeur {true} ou {7}, ou une variable {knows_arthur} ou encore une expression {inventory has (butter, sugar) && some_function()}.

En n’utilisant que les champs obligatoires, notre storylet devient :

=== nom_storylet
#storylet
= content
+ [Quête du talisman sacré] 
…
->->

Lister les storylets dans ink

L’affichage de vos storylets va se faire selon des critères (open/close, catégories, nombre max, aléatoire…) qu’il va falloir exprimer. C’est l’objet de toute la section suivante.
Pour commencer, contentons nous d’un critère simple : je veux 3 storylets. La requête correspondante est "max=3".

Pour exécuter cette requête depuis ink, vous avez 2 options :

  • utiliser un tunnel : -> storylets_tunnel("max=3") ->
  • utiliser un thread : <- storylets_thread("max=3", -> retour)

Dans le premier cas, ink listera les =content des 3 storylets. Votre lecteur prendra l’un de ces choix, puis à un moment, la storylet sera terminée et le tunnel nous ramènera juste après l’appel à storylets_tunnel.

Dans le deuxième cas, c’est pareil mais avec thread, ce qui veut dire 2 choses :

  • vous devez préciser où aller quand la storylet sera terminée (-> retour)
  • vous pouvez ajouter d’autres choix ou d’autres appels à storylets_thread pour cumuler les choix.

Par exemple :

Vous parlez avec Arthur :
-(boucle)
<- storylets_thread("max=3&category=dialogue_quete", -> boucle)
<- storylets_thread("max=1&category=dialogue_meteo", -> boucle)
+ [Quitter Arthur] -> retour_au_village

Format query string

Étant donné que :

  • on veut filer pas mal d’infos au JavaScript pour choisir les storylets selon différents critères mais on veut pas forcément préciser tous les critères possibles la plupart du temps
  • ink ne permet pas de passer de données structurées à une fonction
  • ink ne permet pas de passer un nombre variable de paramètres à une fonction

j’ai décidé de passer par une seule grosse chaîne de caractères. Pour passer les différents critères, j’ai choisi un format connu de tout le monde : la query string.

Keskecé ? C’est la partie d’une URL qui est après le ?. Mais si vous connaissez : title=blabla&action=edit.
C’est standard, c’est facile à parser, ça peut aussi prendre des valeurs multiples (foo=bar&foo=qux donne foo = ["bar", "qux"]), ou pas de valeur foo=1&bar= ou foo=1&bar.

Critères possibles

Pour l’instant, les critères implémentés :

  • max=<nombre> : pour limiter le nombre de storylets choisies
    • exemple : max=3
  • category=<texte> : sélectionne uniquement les storylets de la catégorie demandée. Si plusieurs categories sont données, il faut que les storylets aient toutes les catégories.
    • exemples : category=quest, category=quest&category=phase_3
  • random=<frequency|uniform|0|false|pas de valeur> : pour choisir aléatoirement les storylets
    • frequency : pour choisir plus souvent les storylets avec une plus grande frequency. Par exemple, une storylet avec une frequency de 2 sortira 2 fois plus souvent qu’une avec une frequency de 1.
    • uniform : pour ignorer la frequency et choisir de manière homogène
    • 0 ou false : ne pas choisir aléatoirement, équivalent à ne pas préciser random
    • pas de valeur : tirage aléatoire avec algorithme par défaut (frequency). :warning: Ne pas confondre max=1&random (pas de valeur, donc frequency) avec max=1 (pas de random, donc pas d’aléatoire)
    • exemples : category=dog&random, max=1&random=uniform
  • filter=<nom d'une fonction ink>: pour filtrer selon une logique personnalisée (voir section dédiée)
    • exemple : filter=mon_filtre

Avancé - Filtre personnalisé

Vous pouvez filtrer selon des critères personnalisés avec une fonction implémentée en ink.
Cette fonction sera appelée avec le nom de chaque storylet ouverte pour savoir si vous voulez l’inclure (return true) ou non (return false ou pas de return).
Pour récupérer des infos sur une storylet à partir de son nom, une fonction storylets_get_prop(storylet_name, prop_name, defaut_value) est fournie.

Voici un exemple de filtre :

== function filter_magic_3_to_8(storylet_name)
~ temp magic = storylets_get_prop(storylet_name, "magic", 0)
~ return 3 <= magic && magic <= 8

Ce filtre sélectionnera les storylets qui ont un stitch =magic qui vaut entre 3 et 8. Pour l’utiliser : filter=filter_magic_3_to_8.

1 « J'aime »

L’aléatoire pondéré, j’avais prévu de faire sans, mais ça donne envie.
La possibilité d’utiliser des threads au lieu de tunnels, je crois que c’est plus adapté à ce que je veux faire aussi, mais ce sont des aspects de ink que je ne maitrise pas encore du tout, donc peut-être que je me trompe. C’est cool d’avoir l’option en tout cas.
La documentation me semble claire en tout cas, les parties qui me semblent les plus compliquées sont liées aux éléments de ink que j’ai juste survolés en relisant la doc.

1 « J'aime »

J’ai publié le code : GitHub - floriancargoet/calico-storylets: An alternative to Calico's default storylets patch
Les fichiers qui t’intéressent sont dans patches/.

3 « J'aime »

Super, je récupère ça ! Merci !

Tiens moi au courant ! Bugs, frustrations, satisfactions… Dispo si besoin d’aide pour démarrer :slight_smile:

J’ai commencé un peu. Je n’avance pas vite, mais je dirais que c’est plus ink de base qui me pose souci que le système de narramiettes. Je me demande si markdowntohtml ce n’est pas piégeux aussi, j’ai l’impression qu’il me pose plus de soucis qu’autre chose.

C’est possible oui, il y a conflit entre la syntaxe markdown et ink par moment il me semble.
Courage !

J’en ai parlé sur Discord mais pas ici : nouvelle version !

Les changements :

  • on n’est plus obligé d’utiliser Calico, ça marche aussi avec inkjs tout seul
  • un outil storyletsDebugger pour inspecter ses storylets depuis le navigateur
  • correction d’un bug remonté par @filiaa : on ne pouvait pas utiliser de divert dans les stitches spéciaux des storylets, c’est désormais possible (pratique pour des tests du style TURNS_SINCE(-> x.y.z) >= 3)