One of the biggest tasks I’ve been putting off is figuring out how to handle conversations. Luckily Ron Gilbert once again comes to the rescue with his open source release of Delores. In it, he has a bunch of .yack scripts which I believe he said are loosely based on Ink (scripting language by Inkle). Well I ended up writing my own lua parser which analyzes these .yack files. There are some more advanced features that Ron used that I am not – or I am handling in code someplace else, but for the most part it works. I can put lua code in curly brackets if needed and I can set tags for each dialog line.

  • [random], where if they all have the same grouping it randomly selects one and removes the others.
  • [once] will only show that item once if you click it.
  • [showonce] only shows once regardless if you clicked it or not.
  • [temponce] will show it once if you click it, but then next time you come back to the NPC to talk to them it will appear again.
  • [YACK(“variable_name”) == 1] is treated like an “if statement”. If variable_name == 1, then select that line.

One additional thing that made this a whole lot easier, is I created a state manager, so that I can easily keep track of variables that will need to be saved to JSON. This is a life saver. I can say setProperty(“name”, “property”, true); and it keeps track of it. I can easily loop through all properties and spit them out to json. This function’s last argument takes bools, strings, ints, and floats so I can basically store any value in it that is needed. In the yack file’s init, where I initialize the variable “mumble_counter” to 0, then the YACK function basically just runs setProperty(“example.yack”, “mumble_counter”, 0); then to run the if statement [YACK(“mumble_counter”) == 1], the YACK function would run getProperty(“example.yack”, “mumble_counter”); and then it would compare it to 1.

OK here is an example of a conversation script, .yack file.

=== init ===
{ YACK("mumble_counter", 0) }
-> exit

=== start ===
{ hideVerbMenu() }
player: SAY(12281,"Hi papers!")
papers: SAY(12282,"Hello player!")
-> main  -- Transition to the 'main' section

=== main ===
1 SAY(12283,"What did you mumble?") -> mumble [once]
1 SAY(12284,"Just then! You did it again!") -> mumble [YACK("mumble_counter") == 1] [showonce]
1 SAY(12285,"Whatever <mumble>") -> mumble [YACK("mumble_counter") == 2] [showonce]
2 SAY(12286,"Not a lot to do here, is there?") -> pools
3 SAY(13288,"Yawn...") -> bored [random] [once]
3 SAY(14288,"Ahhhhh!!") -> bored [random] [once]
3 SAY(15288,"I'm tired...") -> bored [random] [once]
4 SAY(12288,"I was thinking about becoming a professor...") -> detective [once]
5 SAY(12289,"Got to run... bye.") -> done

=== pools ===
papers: SAY(12290,"Not really.") [once]
papers: SAY(12291,"There are some pools over there.")
papers: SAY(12292,"They are kind of deep") [once]
papers: SAY(12293,"But I don't like to get wet.")
-> main

=== bored ===
papers: SAY(12290,"Sorry to bore you")
-> main

=== mumble ===
papers: SAY(12295,"I don't understand-what you're asking?") [YACK("mumble_counter") == 0]
papers: SAY(12296,"Are you on drugs? Just say no.") [YACK("mumble_counter") == 1]
papers: SAY(12297,"Hey, the 'mumble' gag is mine!") [YACK("mumble_counter") == 2]
player: SAY(12298,"Ha! Got ya!") [YACK("mumble_counter") == 2]
{ YACK("mumble_counter")++ }
-> main

=== detective ===
player: SAY(12306,"Ever met one?")
papers: SAY(12307,"Can't say that I ever have.")
player: SAY(12308,"Maybe I'll just make a game that has a detective in it.")
player: SAY(12309,"Sounds easier.")
-> main

=== done ===
{ showVerbMenu() }
-> exit

My parser passes the dialog to my engine which queues the dialog up. If it finds an audio file with the ID number, it will play it and display the text. If not, it will just display the text.

Here is a preview of what the above file looks like: