Q6/JS - Building a new conversation system

UPDATE: Based on a reference I found to "array intersections" (not a term I was familiar with), I changed my dialogue array population function. It now properly lists all the correct options in the menu, but they still don't do anything, and I still don't get an error. The bug hunt continues...

(Topic Post)
At the moment, I am almost finished with "Phase 1" of my design plans for a new conversation system. "Phase 2" involves a choice of design implementation and will "makes things prettier" (for now, the menu choices look like code listings, and there are some w.objects that could use replacing with variables), but I want to get the core of it working right, before I make changes.

I've finally got rid of all the errors, but it looks like showMenuWithNumbers is not recognizing my selection;
it always just cancels out with the final else option. I do have a line that clears out the dialogue array, but that should only happen after the menu is done (in theory). I fear the problem is that I am trying to pass a pre-built array, instead of listing the items in the array, as is shown in the samples. This would put a rather big wrench into my gears, as the strings in the array will (eventually) be long strings that I'm trying to keep out of the menu call.
(EDIT: I've already realized, neither of those things could be the problem, because it they were, the one choice that does appear in the menu would also not be present. So, I actually have no idea what's wrong.)
In other words, instead of doing this:

showMenuWithNumbers('Pick a color', ['Blue', 'Red', 'Yellow'], function(result)

I am (effectively) trying to do this:

let colorList =  ['the kind of blue that reminds you of the summer skies of your youth',
  'a Red that express a passionate heart',
  'that deep forest green that calls you to adventure']

showMenuWithNumbers('Pick a color', colorList, function(result)

If that's not the problem, than I'm not sure why it isn't working.
So far, what I have is this:

Details (click me for code):
In code.js, I have a custom function that takes in the name of the NPC calling the function, cross-references an array of "things the player is curious about" with an array of "things this NPC knows about," funnels any matches into an array that is intended to become "choices in a dialogue menu," then tacks on any subjects the NPC knows about that are common enough to always be choices (even if the player isn't specifically curious about them), and finally pushes on a cancel choice.

In data.js, I have the starting "curious" and "dialogue" array attributes set up for the player, and in the Mira NPC object I have a call for the new function followed by the showMenuWithNumbers that tries to use my dialogue array. I also have a kiss cmd set for for Mira, which should add a new string to the curious array. In theory, this should result in:
one topic shared by me.curious and Mira.knows (should be in menu, but is not),
one topic in me.curious that is not in Mira.knows (should not be in menu, and is not),
one topic in Mira.common (should be in menu, and is), and
one topic in Mira.knows that gets added to me.curious (should appear in menu after trying to kiss Mira, but does not)
(the kiss cmd seems to be working fine for itself)
updated code in code.js:
const getDialogue = function(character) {
  let i;
  for (i of w.me.curious) {
    if (character.knows.includes[i]) {
      w.me.dialogue.push(i);
      }
    }
  let n;
  for (n of character.common) {
    w.me.dialogue.push(n);
  }
  w.me.dialogue.push("Cancel")
}

in data.js:
createItem("me", PLAYER(), {
  loc:"belvedere",
  regex:/^(me|myself|player)$/,
  examine: "Just a regular guy.",
  hitpoints:100,
  curious:["castle", "farTown"],
  dialogue:[],
})

createItem("Mira", NPC(true), {
  loc:"belvedere",
  examine:"The new Queen is still quite young, but you recognize a wisdom forged in loss, behind her eyes.",
  knows:["kiss", "castle"],
  common:["highReign"],
  talkto:function() {
    getDialogue(w.Mira);
    showMenuWithNumbers("Talk to Mira about what?", w.me.dialogue, function(result) {
      if (result === "kiss") {
        msg("We don't have time for romance; our Kingdom needs us.");
      }
      else if (result === "castle") {
        msg("Rebuilding the rest of the castle can wait; I want to focus on providing a real town for the people, first. Besides, other construction efforts will improve as the prosperity of the people improves.");
      }
      else if (result === "highReign") {
        msg("High Reign was once a unifying kingdom, bringing the other tribes and kingdoms under a single banner. It would be nice to...</br>Well, we have a lot of work to do before we can start talking politics.");
      }
      else {
        msg("Never mind.");
      }
    })
    w.me.dialogue.splice(0, w.me.dialogue.length);
  },
  kiss:function() {
    msg("Mira interupts your attempt with a firm <i><b>SLAP!</b></i>");
    msg("<b>Mira:</b> Knock it off, tunahead!");
    wait(2);
    msg("<b>Sylphia:</b> I'll kiss you!");
    msg("<b>Tye:</b> ...");
    wait(2);
    msg("Sylphia crosses her arms and pouts.");
    msg("<b>Sylphia:</b> Hmph!");
    if (!w.me.curious.includes["kiss"]) {
      w.me.curious.unshift("kiss");
    }
  }

This is your offending line of code:

w.me.dialogue.splice(0, w.me.dialogue.length);

With that line in its current position (outside of the showMenu code block), result is undefined when the show menu function's function runs. That is because it is outside of the proper code block, which means it executes sooner than you want it to in this scenario.

Try it like this:

  talkto:function() {
    getDialogue(w.Mira);
    showMenuWithNumbers("Talk to Mira about what?", w.me.dialogue, function(result) {
      if (result === "kiss") {
        msg("We don't have time for romance; our Kingdom needs us.");
      }
      else if (result === "castle") {
        msg("Rebuilding the rest of the castle can wait; I want to focus on providing a real town for the people, first. Besides, other construction efforts will improve as the prosperity of the people improves.");
      }
      else if (result === "highReign") {
        msg("High Reign was once a unifying kingdom, bringing the other tribes and kingdoms under a single banner. It would be nice to...<br/>Well, we have a lot of work to do before we can start talking politics.");
      }
      else {
        msg("Never mind.");
      }
      w.me.dialogue.splice(0, w.me.dialogue.length); // Moved here by KV
    })
    //w.me.dialogue.splice(0, w.me.dialogue.length); // Commented out by KV
  },

PS

I edited the above code within 2 minutes after I posted it. So, if you were watching for a response and immediately copied and pasted, note that I originally omitted the 2nd line (getDialogue(w.Mira);). It is fixed now, though.

Also, I just saw this part of your post:

I do have a line that clears out the dialogue array, but that should only happen after the menu is done (in theory).

You almost had it figured out.


Also also, something about the way you posted your code using <details> isn't quite right. I had to view this page's source code to see your code to edit it. Therefore, I believe that's why the &lt;/br&gt; is in my "copy" of your code.

It should be <br/>. I just fixed that in my above code sample, too.


That splice line seems awfully wordy. Wouldn't it be easier to write:

w.me.dialogue.length = 0

(assuming you really need to change any variables that hold a reference to the array, so can't use the much faster

w.me.dialogue = []

)

Yes, it's irrelevant to your problem; but it's the first thing that jumped out at me in that cade.


PPS

To test your code, I inserted a few instances of console.log() here and there to see what was happening behind the scenes during the processes. (This is how I debug my own code.)

  talkto:function() {
    getDialogue(w.Mira);
    console.log("Running talkTo . . .");
    console.log("w.me.dialogue: ", w.me.dialogue);
    showMenuWithNumbers("Talk to Mira about what?", w.me.dialogue, function(result) {
      console.log("Running showMenuWithNumbers . . .");
      console.log("w.me.dialogue: ", w.me.dialogue);
      console.log("result: ", result);
      if (result === "kiss") {
        msg("We don't have time for romance; our Kingdom needs us.");
      }
      else if (result === "castle") {
        msg("Rebuilding the rest of the castle can wait; I want to focus on providing a real town for the people, first. Besides, other construction efforts will improve as the prosperity of the people improves.");
      }
      else if (result === "highReign") {
        msg("High Reign was once a unifying kingdom, bringing the other tribes and kingdoms under a single banner. It would be nice to...<br/>Well, we have a lot of work to do before we can start talking politics.");
      }
      else {
        msg("Never mind.");
      }
      w.me.dialogue.splice(0, w.me.dialogue.length);
    })
    //w.me.dialogue.splice(0, w.me.dialogue.length);
  },

SCREEN-RECORDING

debugging-questjscode


mrangel is like a Javascript wizard, by the way -- just for the record.


With that line in its current position ...
...it executes sooner than you want it to in this scenario...
You almost had it figured out.
...the way you posted your code using <details> isn't quite right. I had to view this page's source code to see your code...
It should be <br/>.
D'OH!
Try it like this:
(It works now)
WOO-HOO! Thank you, K.V.! I will take away the lesson that when going back for subsequent bug checks, I should not ignore things I may have dismissed the first time around.
Now on to Phase 2: tidy the code, pretty the output, and streamline the process.

P.S.
Since I'm basically learning JavaScript as I go along, I had to look up how to clear out an array. The explanation I found mentioned all three ways, but the first two had warnings that I don't fully comprehend, like references trying to find the old array and "may have some potential memory issues." Since I plan to use the same array every time the player talks to any NPC, I thought it best to use the "cleanest" version. I am also hoping to hide that bit inside another function before I'm done, so I won't have to type it in for every time I use the menu.

Console log is a neat trick! I'll have to remember that.

switch is another neat trick.

  talkTo:function(){
    let list = ['Blue', 'Red', 'Yellow'];
    showMenuWithNumbers('Pick a color', list, function(result){
      if(!result) {
        msg("Something messed up.")
        return
      }
      switch (result.toLowerCase()){
        case "blue":
          msg("You chose blue.")
          break
        case "red":
          msg("You picked red.")
          break
        case "yellow":
          msg("You selected yellow.")
          break
        default:
          msg("Something went wrong.")
      }
    })
  },

NOTE

For some reason, there is no response when I type in text that is not a choice. If I enter a number that isn't listed, I get the "Something messed up" response, but nothing prints in-game when I enter "brown".

I believe this might be a bug, but I'm going cross-eyed trying to figure out how io.menuResponse works.


Yeah, I was sort looking into that today too, for different reasons, and it does get pretty mind boggling.
I had to make my own version of showMenuWithNumbers, and that was quite a rabbit hole.

At this point, I have my conversation system all set up (although I suspect it could be tidier code).
I basically copied the showMenuWithNumbers function to make my own slightly modified version, so I have that issue now of what to do if they don't type numbers. I'm thinking a response that says, "Hey! That's not a number!"

That is not an issue with the regular showMenu, as the command bar disappears, until you select something with the mouse.
Apparently, showMenuWithNumbers leaves up the bar on purpose, so you can type a number, but it should really use some kind of direct keyboard response instead.

Not sure it's important enough to me to address that right now, though. I'm about ready to start writing the actual game part of my game (I've got a huge list of items and what not, ready to type in). I will throw up the final version of my code though, so you can see how it turned out. I'm gonna skip the "details" tag this time, since I'm not sure what was wrong with it.

In code file:
I couldn't get the tidy method using "filter" and "indexOf" to work for me with 2D arrays,
so I had to resort to nested loops. I am open to learning a better a way to do this.

/**
*** My dialogue populating function
**/

let arr1D;
let arr2D;

function getDialogue(character) {
  for (arr1D = 0; arr1D < curious.length; arr1D++) {
    for (arr2D = 0; arr2D < character.knows.length; arr2D++) {
      if (curious[arr1D] === character.knows[arr2D][0]) {
        dialogue.push(character.knows[arr2D]);
      }
    }
  }
  let n;
  for (n of character.common) {
    dialogue.push(n);
  }
  dialogue.push(["Cancel", "Never mind"])
}

function show2dDialogue(title, options, fn) {
  const opts = {article:DEFINITE, capital:true, noLinks:true}
  io.input(title, options, false, fn, function(options) {
    for (let i = 0; i < options.length; i++) {
      let s = '<a class="menu-option" onclick="io.menuResponse(' + i + ')">';
      s += (typeof options[i][0] === 'string' ? options[i][0] : lang.getName(options[i][0], opts))
      s += '</a>';
      msg(s);
    }
  })
}

function runDialogue(character) {
  getDialogue(character);
  show2dDialogue("Talk to " + character.name + " about what?", dialogue, function(result) {
    let i;
    for (i = 0; i < dialogue.length; i++) {
      if (result[0] === dialogue[i][0]) {
        msg(dialogue[i][1]);
        dialogue.splice(0, dialogue.length);
      }
    }
  })
}

In data file:
It took some setting up in the code, but each conversation topic is listed within its owner NPC and takes up only a single line.
All the supporting code is gathered into a single function call as well.

createItem("me", PLAYER(), {
  loc:"belvedere",
  regex:/^(me|myself|player)$/,
  examine: "Just a regular guy.",
  hitpoints:100,
  curiosities:["castle", "farTown"],
  dialogueChoices:[],
  puzzleTest:"<b>Sylphia:</b> You solved it already?!",
})

let dialogue = w.me.dialogueChoices;
let curious = w.me.curiosities;

createItem("Mira", NPC(true), {
  loc:"belvedere",
  examine:"The new Queen is still quite young, but you recognize a wisdom forged in loss, behind her eyes.",
  knows:[
    ["No kiss?", "<b>Mira:</b> We can't distract ourselves with romance, now. Our Kingdom needs us."],
    ["Rebuilding the castle", "<b>Mira:</b> Rebuilding the rest of the castle can wait. I want to focus on providing a real town for the people, first. Besides, other construction efforts will improve as the prosperity of the people improves."],
  ],
  common:[
    ["The Kingdom of High Reign", "<b>Mira:</b> High Reign was once a unifying kingdom, bringing the other tribes and kingdoms under a single banner. It would be nice to...<br />Well, we have a lot of work to do, before we can start talking politics."],
  ],
  talkto:function() {
    runDialogue(w.Mira);
  },
  kiss:function() {
    msg("Mira interupts your attempt with a firm <i><b>SLAP!</b></i>");
    msg("<b>Mira:</b> Knock it off, tunahead!");
    msg("<b>Tye:</b> Well, <i>excuse</i> me, Your Highness!");
    msg("Sylphia flies up to your face, with a friendly grin.");
    msg("<b>Sylphia:</b> I'll kiss you!");
    msg("<b>Tye:</b> ...");
    msg("Sylphia crosses her arms and pouts.");
    msg("<b>Sylphia:</b> Hmph!");
    if (!curious.includes["No kiss?"]) {
      curious.unshift("No kiss?");
    }
  },
})

I have had a look at this, and there were some issues.

The showMenuWithNumbers function is designed to take text - the player can type a keyword from the text as an alternative to the number, and it will try to match that to an option. However it failed to check if the number was okay, and the documentation failed to point out you need to handle undefined.

I have added a further option, showMenuNumbersOnly which has no text input - it captures the keypress, and passes that. It ignores any keypress it does not like so no issue with undefined here. You are effectively limited to nine options.

Should all be corrected, but not released, so you need the latest _io.js from Github.


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums