# 06-dialog

Start with 06-dialog-playground

Resources:

# Displaying Clues

Start off inside of inside room6.ts by adding a door and a statue as a MovableEntity:

const door = new Door(
  resources.models.door6,
  {
  	position: new Vector3(28.3, 0.25, 19.75),
  	rotation: Quaternion.Euler(0, 180, 0),
  },
  resources.sounds.doorSqueak
);

const munaStatue = new MovalbeEntity(
  resrouces.models.muna,
  { position: new Vector3(26.748, 0.1054, 20.765) },
  resources.sounds.moveObject1,
  new Vector3(0, 0, 2),
  1.5
);

Then add a keypad similar to room5.ts and wire up the input logic:

// Prep the keypad UI
const keypad = new Keypad(gameCanvas);
keypad.container.visible = false;

// Add a panel which opens the UI when clicked
const numPadLock = new NumPadLock(resources.models.numpad2);
numPadLock.addComponent(
  new OnClick((): void => {
    keypad.container.visible = true;
  })
);

// Wire up the keypad logic
let currentInput = "";
keypad.onInput = (value: number): void => {
  currentInput += value;
  keypad.display(currentInput);
  numPadLock.playButtonPressed();
};
keypad.onReset = (): void => {
  currentInput = "";
  keypad.display(currentInput);
  numPadLock.playButtonPressed();
};
keypad.onSubmit = (): void => {
  if (currentInput == "104") {
    // Correct!
    keypad.display("OK!", Color4.Green());
    numPadLock.playAccessGranted();
    numPadLock.removeComponent(OnClick);
    munaStatue.getComponent(utils.ToggleComponent).toggle();
    numPadLock.addComponentOrReplace(
      new utils.Delay(2000, (): void => {
        keypad.container.visible = false;
        door.openDoor();
      })
    );
  } else {
    // The password is incorrect
    keypad.display("Err", Color4.Red());
    numPadLock.playAccessDenied();
    currentInput = "";
  }
};

Inside of the gameObjects folder add a new file called spotlight.ts:

import resources from "../resources";
import utils from "../../node_modules/decentraland-ecs-utils/index";

export class Spotlight extends Entity {
  constructor(transform: TranformConstructorArgs, hiddenNumberValue: string) {
    super();
    engine.addEntity(this);

    this.addComponent(new Transform(transform));
    this.addComponent(new AudioSource(resources.sounds.spotlight));

    this.addComponent(
      new utils.ToggleComponent(utils.ToggleState.Off, value => {
        if (value == utils.ToggleState.On) {
          this.addComponent(resources.models.spotlight);

          const hiddenNumber = new Entity();
          hiddenNumber.addComponent(new TextShape());
          hiddenNumber.getComponent(TextShape).value = hiddenNumberValue;
          hiddenNumber.getComponent(TextShape).fontSize = 5;

          hiddenNumber.setParent(this);
          hiddenNumber.addComponent(
            new Transform({ position: new Vector3(0, 0.9, -0.4) })
          );

          this.getComponent(AudioSource).playOnce();
        }
      })
    );
  }
}

Now back in room6.ts add some spotlights around the muna statue:

// Spotlights
const spotLight1 = new Spotlight(
  {
    position: new Vector3(-0.04, 0, 0)
  },
  "1"
);
spotLight1.setParent(munaStatue);
const spotLight2 = new Spotlight(
  {
    position: new Vector3(-0.02, 0, 0),
    rotation: Quaternion.Euler(0, 90, 0)
  },
  "0"
);
spotLight2.setParent(munaStatue);
const spotLight3 = new Spotlight(
  {
    position: new Vector3(-0.03, 0, 0),
    rotation: Quaternion.Euler(0, 180, 0)
  },
  "4"
);
spotLight3.setParent(munaStatue);

Inside of the ui folder add a new file called munaDialog.ts:

import resources from "../resources";
import { SimpleDialog } from "../modules/simpleDialog";

function selectRandom(options: string[]): string {
  return options[Math.floor(Math.random() * (options.length - 1))];
}

export class MunaDialog extends SimpleDialog {
  private dialogTree: SimpleDialog.DialogTree;

  public onCorrectAnswer: (questionId: number) => void;

  constructor(gameCanvas: UICanvas) {
    // Create a new SimpleDialog to manage the dialog tree
    super({
      canvas: gameCanvas,
      leftPortrait: {
        width: 256,
        height: 256,
        sourceWidth: 256,
        sourceHeight: 256,
        positionX: "-17%"
      },
      rightPortrait: {
        width: 256,
        height: 256,
        sourceWidth: 256,
        sourceHeight: 256,
        positionX: "15%"
      },
      dialogText: {
        width: "25%",
        height: "25%",
        textSpeed: 15,
        textIdleTime: 5,
        textConfig: { fontSize: 16, paddingLeft: 25, paddingRight: 25 },
        background: resources.textures.textContainer,
        backgroundConfig: { sourceWidth: 512, sourceHeight: 257 }
      },
      optionsContainer: {
        stackOrientation: UIStackOrientation.VERTICAL,
        spacing: 0,
        width: "40%",
        height: "12%",
        vAlign: "top",
        hAlign: "center",
        positionY: "-65%",
        background: resources.textures.optionsContainer,
        backgroundConfig: { sourceWidth: 512, sourceHeight: 79 },
        optionsTextConfig: { fontSize: 20, paddingLeft: 20, positionY: "-35%" }
      }
    });

    // Some random replies for muna
    const randomStartingOptions = ["I see...", "...", "...OK..."];
    const randomWrongAnswers = [
      "You are just guessing...",
      "No it is not...",
      "What? Not even close!"
    ];

    // Variables used in the dialog tree
    let firstTimeDialog = true;
    let firstOptionCorrect = false;
    let secondOptionCorrect = false;
    let thirdOptionCorrect = false;

    // Dialog text colors
    const npcColor = Color4.White();
    const playerColor = new Color4(0.898, 0, 0.157);

    this.dialogTree = new SimpleDialog.DialogTree()
      .if(() => firstTimeDialog)
      .call(() => (firstTimeDialog = false))
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitDefault
      )
      .say(() => "Hi there stranger!", { color: npcColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitSurprised
      )
      .say(() => "A talking dog statue?!", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(
        () =>
          "You're a talking bear yourself... you don't see me making any judgements.",
        { color: npcColor }
      )
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitThinking
      )
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitDefault
      )
      .say(() => "Anyway... how do I get out of this place?", {
        color: playerColor
      })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitThinking
      )
      .say(
        () =>
          "You'll have to pass through me. And I'll only let you if you answer my three questions.",
        { color: npcColor }
      )
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitSurprised
      )
      .say(
        () =>
          "So go ahead, explore the other rooms and solve the puzzles to find the answers to my questions!",
        { color: npcColor }
      )
      .say(() => "Um... sure, why not? Who am I to argue?", {
        color: playerColor
      })
      .wait(3)
      .else()
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitDefault
      )
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitThinking
      )
      .if(() => firstOptionCorrect && secondOptionCorrect && thirdOptionCorrect)
      .say(() => "We're done talking. \nEnter the code and you can leave.", {
        color: npcColor
      })
      .wait(3)
      .else()
      .say(() => "Did you solve my puzzles? Do you know the answers?", {
        color: npcColor
      })
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitThinking
      )
      .beginOptionsGroup()
      .option(() => "- Yes.")
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitDefault
      )
      .say(() => "Yes. Why do you think I came all the way down here?", {
        color: playerColor
      })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => selectRandom(randomStartingOptions), { color: npcColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitThinking
      )
      .say(() => "Very well then... answer me this:", { color: npcColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitDefault
      )
      .if(() => !firstOptionCorrect)
      .say(() => "What�s my favorite color?", { color: npcColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitThinking
      )
      .beginOptionsGroup()
      .option(() => "- Green.")
      .say(() => "Is it green?", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => selectRandom(randomWrongAnswers), { color: npcColor })
      .endOption()
      .option(() => "- Blue.")
      .say(() => "Blue... right?", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => selectRandom(randomWrongAnswers), { color: npcColor })
      .endOption()
      .option(() => "- Orange.")
      .say(() => "Organge!", { color: playerColor })
      .call(() => (firstOptionCorrect = true))
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => "That�s right!", { color: npcColor })
      .say(() => '"In the midst of darkness, light persists."', {
        color: npcColor
      })
      .call(() => this.onCorrectAnswer(0))
      .endOption()
      .endOptionsGroup()
      .else()
      .if(() => !secondOptionCorrect)
      .say(() => "What�s my favorite game?", { color: npcColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitThinking
      )
      .beginOptionsGroup()
      .option(() => "- Retro arcade games.")
      .say(() => "Is it retro arcade games?", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => selectRandom(randomWrongAnswers), { color: npcColor })
      .endOption()
      .option(() => "- Darts.")
      .say(() => "Darts?", { color: playerColor })
      .call(() => (secondOptionCorrect = true))
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => "Yes it is...", { color: npcColor })
      .say(() => '"Give light, and the darkness will disappear of itself."', {
        color: npcColor
      })
      .call(() => this.onCorrectAnswer(1))
      .endOption()
      .option(() => "- Bowling.")
      .say(() => "Of course� It�s bowling... right?", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => selectRandom(randomWrongAnswers), { color: npcColor })
      .endOption()
      .endOptionsGroup()
      .else()
      .if(() => !thirdOptionCorrect)
      .say(() => "What�s my favorite dessert?", { color: npcColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitThinking
      )
      .beginOptionsGroup()
      .option(() => "- Cheese Cake.")
      .say(() => "Cheese Cake?", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => selectRandom(randomWrongAnswers), { color: npcColor })
      .endOption()
      .option(() => "- Apple Pie.")
      .say(() => "It's Apple Pie...", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => selectRandom(randomWrongAnswers), { color: npcColor })
      .endOption()
      .option(() => "- Lemon Pie.")
      .say(() => "Lemon Pie!", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .call(() => (thirdOptionCorrect = true))
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => "Very good...", { color: npcColor })
      .say(() => '"Give light and people will find the way."')
      .call(() => this.onCorrectAnswer(2))
      .endOption()
      .endOptionsGroup()
      .endif()
      .endif()
      .endif()
      .endOption()
      .option(() => "- No, not yet")
      .showPortrait(
        SimpleDialog.PortraitIndex.LEFT,
        resources.textures.playerPortraitDefault
      )
      .say(() => "No, not yet", { color: playerColor })
      .showPortrait(
        SimpleDialog.PortraitIndex.RIGHT,
        resources.textures.npcPortraitSurprised
      )
      .say(() => "You are wasting my time.", { color: npcColor })
      .endOption()
      .endOptionsGroup()
      .endif();
  }

  public run(): void {
    if (!this.isDialogTreeRunning()) {
      this.runDialogTree(this.dialogTree);
    }
  }
}

Now create a new munaDialog inside of room6.ts and wire the logic for onCorrectAnswer:

// Define the dialog tree
const dialog = new MunaDialog(gameCanvas);

// Kick off the dialog when the statue is clicked
munaStatue.addComponent(
  new OnClick((): void => {
    dialog.run();
  })
);

// Reveal the hints as the player answers questions correctly.
dialog.onCorrectAnswer = (questionId: number) => {
  if (questionId === 0) {
    spotLight1.getComponent(utils.ToggleComponent).set(utils.ToggleState.On);
  } else if (questionId === 1) {
    spotLight2.getComponent(utils.ToggleComponent).set(utils.ToggleState.On);
  } else {
    spotLight3.getComponent(utils.ToggleComponent).set(utils.ToggleState.On);
  }
};