Skip to content

Quest Tracker

Build a quest tracker panel — show completion status for each quest (checkmark or X), display gold rewards in real time. When a player completes a quest, automatically pop an achievement notification and hand out the reward. This recipe teaches you how to wire it up with variables, behaviors, and the message renderer.


What you'll build

A quest tracker panel embedded in the chat interface:

  • Quest list — each quest shows its name and completion status (done = green checkmark, not done = red X)
  • Gold counter — real-time display of the player's current gold
  • Auto-detection — when the player's message contains a keyword (e.g. "herb" or "defeat"), the quest is automatically marked complete
  • Achievement notification — a gold-colored toast pops up when a quest is completed, telling the player how much they earned
  • Gold rewards — each quest automatically pays out gold on completion
Player sends a message mentioning "found the herbs"
  → Engine detects player keyword "herb"
  → Checks condition: quest_1_complete == false?
    → Yes: set quest_1_complete = true, add 30 gold, pop achievement notification
    → No: do nothing (quest already completed)
  → Quest panel auto-updates: "Find Herbs" changes from ✗ to ✓

How it works

This quest system uses three core mechanics:

  1. Boolean variables + keyword triggers — each quest is tracked by a boolean variable. When the player's message contains a specific keyword, a behavior rule automatically sets the variable to true
  2. Condition checks — behaviors check whether the quest is already complete before firing. Completed quests won't trigger again (no double rewards)
  3. Message renderer reads variables — the panel reads quest status and gold from variables in real time, dynamically rendering checkmarks or X marks

Step by step

Step 1: Create variables

We need 5 variables — two for quest completion status, one for gold, and two more for quest names (so the message renderer can display them dynamically).

Editor → sidebar → Variables tab → click "Add Variable" for each one

Variable 1: Quest 1 completion status

FieldValueWhy
NameQuest 1 CompleteA label for your own reference in the variable list
IDquest_1_completeBehaviors and the message renderer use this ID to read/write the value
TypeBooleanOnly two states: "done" and "not done"
Default ValuefalseQuest hasn't been completed when a new session starts
CategoryFlagThis is a status flag, not a numeric stat
Behavior RulesSet to true when the player completes the Find Herbs quest. Behaviors auto-detect this via keywords, but you may also mark it complete at an appropriate story moment.Tells the AI what this variable means and when it should change

Variable 2: Quest 2 completion status

FieldValueWhy
NameQuest 2 CompleteEasy to identify
IDquest_2_completeUsed by behaviors and the message renderer
TypeBooleanSame two-state setup
Default ValuefalseNot completed at session start
CategoryFlagStatus flag
Behavior RulesSet to true when the player defeats the Forest Wolf. Behaviors auto-detect this via keywords, but you may also mark it complete at an appropriate story moment.Tells the AI what this variable means and when it should change

Variable 3: Gold

FieldValueWhy
NameGoldEasy to identify
IDgoldAutomatically increases when quests are completed
TypeNumberGold is numeric — needs addition and subtraction
Default Value0No gold at session start — earn it by completing quests
Min Value0Prevents gold from going negative
CategoryResourceGold is a resource variable
Behavior RulesGold is automatically awarded on quest completion. You may also add or subtract gold in the story — e.g., combat loot, trading, or theft.Tells the AI that gold can change in multiple contexts

Variable 4: Quest 1 name

FieldValueWhy
NameQuest 1 NameEasy to identify
IDquest_1_nameThe message renderer uses this ID to display the quest name
TypeStringQuest names are text
Default ValueFind HerbsThe name of the first quest
CategoryCustomJust descriptive data
Behavior RulesDo not modify this variable.Quest names shouldn't be changed

Variable 5: Quest 2 name

FieldValueWhy
NameQuest 2 NameEasy to identify
IDquest_2_nameUsed by the message renderer
TypeStringQuest names are text
Default ValueDefeat the Forest WolfThe name of the second quest
CategoryCustomDescriptive data
Behavior RulesDo not modify this variable.Quest names shouldn't be changed

Why write behavior rules for every variable?

Because the AI can "suggest" variable changes when generating replies. If you don't tell it to leave a variable alone, it might mark the quest complete on its own (e.g., the AI decides "the player found herbs" and sets quest_1_complete to true — but since it bypassed the behavior logic, no gold reward gets paid). The behavior rules field is your instruction to the AI — once written, the AI knows these variables are system-controlled.


Step 2: Create behaviors

This is the heart of the quest system. We need 2 behaviors, each detecting a keyword and marking the corresponding quest complete while handing out rewards.

Editor → Behaviors tab → click "Add Behavior" for each one

Behavior 1: Complete quest "Find Herbs"

WHEN (when to check):

FieldValueWhy
Trigger typePlayer said keyword (keyword)Fires when the player's message contains specific text
Keywordsherb or found herbMatches when the player says something like "I found the herbs"

How does keyword matching work? The engine checks the content of the player's message — if it contains the keyword anywhere, it matches. So "I found the herbs in the cave" triggers because it contains "herb". If you also want to detect keywords in the AI's response, create a separate behavior with the trigger type set to "AI said keyword" (ai-keyword).

ONLY IF (conditions):

VariableOperatorValueWhy
quest_1_completeequals (eq)falseOnly triggers when the quest hasn't been completed yet — prevents double rewards

Why do you need a condition? Without it, every time anyone mentions "herb" the reward fires again. With quest_1_complete == false, the first mention of herb → completes the quest, pays the reward, marks true. Any mention after that → condition fails (already true), nothing happens.

DO (actions):

Add these actions in order:

Action typeSettingsEffect
Modify variableVariable quest_1_complete, operation set, value trueMark quest as completed
Modify variableVariable gold, operation add, value 30Pay out 30 gold reward
Show notificationMessage Quest Complete: Find Herbs! +30 gold, style achievementPop a gold-colored achievement toast
Tell AIContent: The player just completed the quest "Find Herbs" and received 30 gold as a reward. Please acknowledge this in your response.Let the AI know what happened so it can write a better narrative transition

Why "Tell AI"? Modifying variables and showing notifications are silent system operations — the AI itself doesn't know "a quest was just completed." Adding this step lets the AI write a natural follow-up in its next reply (e.g., "You carefully tuck the herbs into your pack, remembering the village elder's request. The trip wasn't for nothing after all").

Behavior 2: Complete quest "Defeat the Forest Wolf"

WHEN (when to check):

FieldValueWhy
Trigger typePlayer said keyword (keyword)Same as above — player keyword trigger
Keywordsdefeat and wolfBoth words must appear — prevents "I saw a wolf" from triggering

Multiple keyword matching logic. When you enter multiple keywords, the message must contain all of them to trigger. So "I defeated the forest wolf" triggers (contains both "defeat" and "wolf"), but "I spotted a wolf" does not (only "wolf", no "defeat").

ONLY IF (conditions):

VariableOperatorValueWhy
quest_2_completeequals (eq)falseSame — prevents repeated triggering

DO (actions):

Action typeSettingsEffect
Modify variableVariable quest_2_complete, operation set, value trueMark quest as completed
Modify variableVariable gold, operation add, value 50Pay out 50 gold (defeating the wolf is harder, so the reward is bigger)
Show notificationMessage Quest Complete: Defeat the Forest Wolf! +50 gold, style achievementPop a gold-colored achievement toast
Tell AIContent: The player just completed the quest "Defeat the Forest Wolf" and received 50 gold as a reward. Please acknowledge this in your response.Let the AI know what happened

Action execution order

Actions within a single behavior execute in sequence. So: mark complete → add gold → pop notification → tell AI. This order matters — marking the variable first ensures all subsequent logic is based on the latest state.


Step 3: Build the quest tracker panel (message renderer)

This is the key step that makes the quest panel appear in the chat interface.

Editor → Message Renderer section → select Custom TSX → paste the following code:

tsx
export default function Renderer({ content, renderMarkdown, messageIndex }) {
  const api = useYumina();
  const msgs = api.messages || [];
  const isLastMsg = messageIndex === msgs.length - 1;

  // Read variables
  const quest1Done = api.variables.quest_1_complete === true;
  const quest2Done = api.variables.quest_2_complete === true;
  const quest1Name = String(api.variables.quest_1_name || "Find Herbs");
  const quest2Name = String(api.variables.quest_2_name || "Defeat the Forest Wolf");
  const gold = Number(api.variables.gold ?? 0);

  // Quest list data
  const quests = [
    { name: quest1Name, done: quest1Done, reward: 30 },
    { name: quest2Name, done: quest2Done, reward: 50 },
  ];

  const completedCount = quests.filter(q => q.done).length;

  return (
    <div>
      {/* Render message text normally */}
      <div
        style={{ color: "#e2e8f0", lineHeight: 1.7 }}
        dangerouslySetInnerHTML={{ __html: renderMarkdown(content) }}
      />

      {/* Quest tracker panel — only shown on the last message */}
      {isLastMsg && (
        <div style={{
          marginTop: "16px",
          padding: "16px",
          background: "linear-gradient(135deg, rgba(30,41,59,0.8), rgba(15,23,42,0.9))",
          borderRadius: "12px",
          border: "1px solid #334155",
        }}>
          {/* Panel header */}
          <div style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            marginBottom: "14px",
          }}>
            <div style={{
              fontSize: "15px",
              fontWeight: "bold",
              color: "#e2e8f0",
              letterSpacing: "0.5px",
            }}>
              Quest Tracker
            </div>
            {/* Gold counter */}
            <div style={{
              display: "flex",
              alignItems: "center",
              gap: "6px",
              padding: "4px 12px",
              background: "rgba(234,179,8,0.15)",
              border: "1px solid rgba(234,179,8,0.3)",
              borderRadius: "20px",
            }}>
              <span style={{ fontSize: "14px" }}>💰</span>
              <span style={{
                fontSize: "14px",
                fontWeight: "bold",
                color: "#fbbf24",
              }}>
                {gold}
              </span>
            </div>
          </div>

          {/* Progress indicator */}
          <div style={{
            fontSize: "12px",
            color: "#64748b",
            marginBottom: "12px",
          }}>
            Completed {completedCount}/{quests.length}
          </div>

          {/* Quest list */}
          <div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
            {quests.map((quest, idx) => (
              <div
                key={idx}
                style={{
                  display: "flex",
                  justifyContent: "space-between",
                  alignItems: "center",
                  padding: "10px 14px",
                  background: quest.done
                    ? "rgba(34,197,94,0.08)"
                    : "rgba(30,41,59,0.5)",
                  border: quest.done
                    ? "1px solid rgba(34,197,94,0.2)"
                    : "1px solid #1e293b",
                  borderRadius: "8px",
                }}
              >
                {/* Left side: quest name */}
                <div style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "10px",
                }}>
                  <span style={{
                    fontSize: "13px",
                    color: quest.done ? "#94a3b8" : "#e2e8f0",
                    textDecoration: quest.done ? "line-through" : "none",
                  }}>
                    {quest.name}
                  </span>
                </div>

                {/* Right side: status badge */}
                <div style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "8px",
                }}>
                  {/* Reward amount */}
                  <span style={{
                    fontSize: "12px",
                    color: quest.done ? "#4ade80" : "#64748b",
                  }}>
                    {quest.done ? `+${quest.reward} g` : `${quest.reward} g`}
                  </span>

                  {/* Completion status badge */}
                  <span style={{
                    display: "inline-flex",
                    alignItems: "center",
                    justifyContent: "center",
                    width: "24px",
                    height: "24px",
                    borderRadius: "6px",
                    fontSize: "13px",
                    fontWeight: "bold",
                    background: quest.done
                      ? "rgba(34,197,94,0.2)"
                      : "rgba(239,68,68,0.15)",
                    color: quest.done ? "#4ade80" : "#f87171",
                    border: quest.done
                      ? "1px solid rgba(34,197,94,0.3)"
                      : "1px solid rgba(239,68,68,0.25)",
                  }}>
                    {quest.done ? "✓" : "✗"}
                  </span>
                </div>
              </div>
            ))}
          </div>

          {/* All quests complete banner */}
          {completedCount === quests.length && (
            <div style={{
              marginTop: "12px",
              padding: "10px",
              background: "rgba(34,197,94,0.1)",
              border: "1px solid rgba(34,197,94,0.25)",
              borderRadius: "8px",
              textAlign: "center",
              fontSize: "13px",
              color: "#4ade80",
              fontWeight: "600",
            }}>
              All quests complete!
            </div>
          )}
        </div>
      )}
    </div>
  );
}

Code walkthrough

Don't be intimidated by the length — what it does is very straightforward. Let's go section by section:

Basic setup

tsx
const api = useYumina();
const msgs = api.messages || [];
const isLastMsg = messageIndex === msgs.length - 1;
  • useYumina() — gets the Yumina API so you can read variables
  • isLastMsg — checks whether this is the last message. The quest panel only shows below the last message, so you don't get a duplicate panel on every message

Reading variables

tsx
const quest1Done = api.variables.quest_1_complete === true;
const quest2Done = api.variables.quest_2_complete === true;
const quest1Name = String(api.variables.quest_1_name || "Find Herbs");
const quest2Name = String(api.variables.quest_2_name || "Defeat the Forest Wolf");
const gold = Number(api.variables.gold ?? 0);
  • === true — strict comparison, ensures only boolean true counts as done. Prevents "true" (string) or 1 (number) from being misinterpreted
  • String(... || "Find Herbs") — reads the quest name, falls back to a default if the variable doesn't exist
  • Number(... ?? 0) — converts gold to a number. ?? 0 means "use 0 if the variable doesn't exist"

Quest list data

tsx
const quests = [
  { name: quest1Name, done: quest1Done, reward: 30 },
  { name: quest2Name, done: quest2Done, reward: 50 },
];
const completedCount = quests.filter(q => q.done).length;

Collects quest info into an array so you can loop over it with .map(). completedCount tallies how many are done, used for the progress display.

Status badge

tsx
<span style={{
  background: quest.done
    ? "rgba(34,197,94,0.2)"    // done → green background
    : "rgba(239,68,68,0.15)",  // not done → red background
  color: quest.done ? "#4ade80" : "#f87171",
}}>
  {quest.done ? "✓" : "✗"}
</span>

Each quest has a small badge on the right — green checkmark for done, red X for not done. This is the badge component effect.

Gold counter

tsx
<div style={{
  padding: "4px 12px",
  background: "rgba(234,179,8,0.15)",
  borderRadius: "20px",
}}>
  💰 {gold}
</div>

A pill-shaped gold display in the top-right corner of the panel. Every time a quest is completed, the gold increases and the panel automatically refreshes to show the new value.

All-complete banner

tsx
{completedCount === quests.length && (
  <div style={{ /* green highlight styles */ }}>
    All quests complete!
  </div>
)}

When every quest is done, a green text line appears at the bottom of the panel. completedCount === quests.length checks whether the done count equals the total.

Don't want to write code yourself? Use Studio AI

Editor top bar → click "Enter Studio" → AI Assistant panel → describe what you want in plain language (e.g., "build a quest tracker panel that shows quest completion status and gold"), and the AI will generate the code for you.


Step 4: Save and test

  1. Click Save at the top of the editor
  2. Click Start Game or go back to the home page and open a new session
  3. You'll see the quest tracker panel below the AI's reply: two red X-marked quests, 0 gold
  4. Send a message containing the keyword (e.g., "I found the herbs") — your message contains "herb", the behavior fires immediately, the panel updates: "Find Herbs" switches to a green checkmark, gold becomes 30, an achievement notification pops up
  5. Send another message with the keyword (e.g., "I defeated the forest wolf") — your message contains both "defeat" and "wolf", the second quest completes, gold increases to 80
  6. Once both quests are done, a green "All quests complete!" banner appears at the bottom of the panel

If something isn't working:

SymptomLikely causeFix
Quest panel doesn't appearMessage renderer code wasn't saved or has a syntax errorCheck the compile status at the bottom of the message renderer — it should show a green "OK"
Sent a message with "herb" but quest didn't completeThe behavior keyword doesn't match your actual wordingMake sure your message actually contains "herb". Note: the trigger is a player keyword trigger — it only checks the player's message, not the AI's reply
Quest completed but gold didn't changeMissing the "modify variable gold add" action in the behaviorGo back to the behavior editor and confirm there's a "modify variable gold add 30" action after the "modify variable quest_1_complete" action
Same quest keeps giving repeat rewardsNo condition configuredMake sure the behavior's ONLY IF condition includes quest_1_complete eq false — only triggers when not yet completed
Panel doesn't update in real timeNormal — the panel refreshes on the next messageThe variable has already changed; wait for the AI's reply or send another message and the panel will update automatically
Notification didn't pop upMissing the "show notification" action in the behaviorConfirm there's a show notification action in the action list with style set to achievement
"Defeat the wolf" quest doesn't triggerBoth keywords must appear in the same messageMake sure your message contains both "defeat" and "wolf". If you wrote "I beat the wolf", you'd need to change the keyword to "beat" or add "beat" as an alternative

Going further: expanding the quest system

Once you've got the basics down, you can build on top of this foundation.

Adding more quests

Add a new boolean variable (quest_3_complete) and string variable (quest_3_name) in the Variables tab, then create a matching keyword-triggered behavior in the Behaviors tab. Finally, add a line to the quests array in the message renderer:

tsx
const quests = [
  { name: quest1Name, done: quest1Done, reward: 30 },
  { name: quest2Name, done: quest2Done, reward: 50 },
  { name: quest3Name, done: quest3Done, reward: 100 },
];

Letting the AI assign quests

You can build a "quest accepted" flow — the AI describes a new quest in dialogue, then a behavior detects a specific keyword and dynamically updates the quest name variable:

Action typeSettings
Modify variableVariable quest_3_name, operation set, value Escort the merchant to safety
Show notificationMessage New Quest: Escort the merchant to safety, style achievement

Combining with a shop system

Gold earned from quests can be spent in a shop. See Recipe #3 (Shop & Trading) — use the same gold variable. The quest system adds gold, the shop system deducts it. Both systems share a single economy.

Quest chains

You can use condition combinations in behaviors to create complex quest dependencies. For example, "you can only accept 'Save the Village' after completing 'Find Herbs'":

VariableOperatorValue
quest_1_completeequals (eq)true
quest_3_completeequals (eq)false

Both conditions must be satisfied to trigger — ensures the prerequisite quest is done and the current quest hasn't been completed yet.


Quick reference

What you wantHow to do it
Track quest completionCreate a boolean variable, default false, category Flag
Detect keyword to complete a questBehavior trigger type "Player said keyword" (keyword), enter keywords
Prevent repeat triggersAdd quest_complete eq false in the behavior's conditions
Pop an achievement toast on completionBehavior action: show notification, style achievement
Award gold on completionBehavior action: modify variable, gold add amount
Let the AI know a quest was completedBehavior action: tell AI, write a sentence explaining what happened
Display the quest panelRead variables in the message renderer, render checkmarks/X marks and gold
Only show panel on the last messageCheck isLastMsg in the message renderer
Strike through completed questsUse the textDecoration: "line-through" style
Show completion progressUse quests.filter(q => q.done).length to count
Special banner when all quests are doneCheck completedCount === quests.length

Try it yourself — importable demo world

Download this JSON and import it to experience the full quest tracking system:

recipe-6-demo.json

How to import:

  1. Go to Yumina → My WorldsCreate New World
  2. In the editor, click More ActionsImport Package
  3. Select the downloaded .json file
  4. A new world is created with all variables, behaviors, and renderer pre-configured
  5. Start a new session and try it out

What's included:

  • 5 variables (quest_1_complete and quest_2_complete for quest status, gold for currency, quest_1_name and quest_2_name for quest names)
  • 2 behaviors (Find Herbs completion + Defeat the Forest Wolf completion, each with condition checks, variable modifications, notifications, and tell-AI actions)
  • A message renderer (quest tracker panel: quest list + status badges + gold counter + completion progress)

This is Recipe #6

Earlier recipes covered scene jumping, combat systems, shop & trading, and character creation. This recipe teaches you how to build a quest tracking system using boolean variables + keyword triggers + condition checks. The same pattern extends to achievement systems, story progress tracking, side-quest trees — anything that needs the loop of "detect event → mark state → pay reward → update UI."