# Quests (/nqtr/quest)



The quest system is a way to create and manage quests in your game. Quests can have multiple stages, and players can complete them to earn rewards or progress in the story.

Initialize [#initialize]

To initialize a `quest`, create a new instance of the `QuestBaseModel` class (or your [custom class](#custom-class)) and add it to the game quest dictionary when the game is initialized.

<Callout type="info">
  It is recommended to import the instances at project startup, see the `src/main.ts` file.
</Callout>

To create a new instance of `QuestBaseModel`, you need the following parameters:

<CodeBlockTabs defaultValue="values/aliceQuest.ts">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="values/aliceQuest.ts">
      values/aliceQuest.ts
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="main.ts">
      main.ts
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="values/aliceQuest.ts">
    ```ts
    import { QuestBaseModel, RegisteredQuests, StageBaseModel } from "@drincs/nqtr";
    import { orderProduct, takeProduct } from "../quests";
    import { mcRoom, terrace } from "../rooms";

    export const aliceQuest = new QuestBaseModel(
        "aliceQuest",
        [
            // stages
            new StageBaseModel("talk_alice1", {
                name: "Talk to Alice",
                description: "Talk to Alice on the terrace",
            }),
            new StageBaseModel("order_products", {
                onStart: () => {
                    mcRoom.addActivity(orderProduct);
                },
                name: "Order products",
                description: "Order the products with your PC",
            }),
            new StageBaseModel("take_products", {
                onStart: (_, { notify }) => {
                    terrace.addActivity(takeProduct);
                    notify("You can take the products on the Terrace");
                },
                name: "Take products",
                description: "Take products on the Terrace",
                requestDescriptionToStart:
                    "Wait for the products you ordered to arrive (2 day)",
                deltaDateRequired: 2,
            }),
            new StageBaseModel("talk_alice2", {
                name: "Talk to Alice",
                description: "Talk to Alice on the terrace",
            }),
        ],
        {
            // props
            name: "Help Alice",
            description:
                'To learn more about how the repo works, Talk to Alice. \nGoing when she is there will automatically start an "Event" (see aliceQuest.tsx to learn more). \nAfter that an action will be added to open the pc, in MC room. \n\n(during the quest you can talk to Alice and you will see her talking during the quests of the same Quest)',
            image: "alice_terrace0A",
            onStart: (quest, { notify, uiTransition }) => {
                notify(
                    uiTransition("notify_quest_is_started", { quest: quest.name }),
                );
            },
            onContinue: (stage, { notify, uiTransition }) => {
                notify(
                    uiTransition("notify_quest_is_updated", { quest: stage.name }),
                );
            },
        },
    );

    RegisteredQuests.add(aliceQuest);
    ```
  </CodeBlockTab>

  <CodeBlockTab value="main.ts">
    ```ts
    import "./values/aliceQuest";

    // ...
    ```
  </CodeBlockTab>
</CodeBlockTabs>

`RegisteredQuests.add` is **required** to save the `quests` in the game.

You can also create a function to load `quests`. The important thing is that it is called at least once before the `quests` are used in the game, otherwise they will not be available.

Start [#start]

To start a `quest`, you can use the `start` method of the Quest class. This method will set the current stage to the first stage of the quest and trigger the `onStart` action defined in the quest. This function has the following parameters:

* `props`: the properties that will be passed to `onStart`. Its interface corresponds to <DynamicLink href="/nqtr#onrunprops">`OnRunProps`</DynamicLink>.

<CodeBlockTabs defaultValue="Typescript">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Typescript">
      Typescript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="ink">
      ink
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Typescript">
    ```ts  title="content/labels/start.label.ts" groupId="narrative_language"
    import { newLabel } from "@drincs/pixi-vn";
    import { aliceQuest } from "../values/quests";

    const startLabel = newLabel("start", [
        async (props) => {
            await aliceQuest.start(props); // [!code focus]
        },
    ]);
    export default startLabel;
    ```
  </CodeBlockTab>

  <CodeBlockTab value="ink">
    ```ink  title="ink/start.ink" groupId="narrative_language"
    === start ===
    //  {aliceQuest} the id of the quest
    # start quest {aliceQuest}
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Continue [#continue]

To go to the next stage of a `quest`, you can use the `continue` method of the Quest class. This method will set the current stage to the next stage of the quest and trigger the `onContinue` action defined in the quest. This function has the following parameters:

* `props`: the properties that will be passed to `onContinue`. Its interface corresponds to <DynamicLink href="/nqtr#onrunprops">`OnRunProps`</DynamicLink>.

<CodeBlockTabs defaultValue="Typescript">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Typescript">
      Typescript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="ink">
      ink
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Typescript">
    ```ts  title="content/labels/start.label.ts" groupId="narrative_language"
    import { newLabel } from "@drincs/pixi-vn";
    import { aliceQuest } from "../values/quests";

    const startLabel = newLabel("start", [
        async (props) => {
            await aliceQuest.continue(props); // [!code focus]
        },
    ]);
    export default startLabel;
    ```
  </CodeBlockTab>

  <CodeBlockTab value="ink">
    ```ink  title="ink/start.ink" groupId="narrative_language"
    === start ===
    //  {aliceQuest} the id of the quest
    # continue quest {aliceQuest}
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<Accordions>
  <Accordion title="go_next_only_if_is_completed" id="go-next-only-if-is-completed">
    To go to the next stage of a `quest` only if the current stage is completed, you can use the `advanceIfCompleted` method of the Quest class. This method will check if the current stage is completed and then set the current stage to the next stage of the quest and trigger the `onContinue` action defined in the quest. This function has the following parameters:

    * `props`: the properties that will be passed to `onContinue`. Its interface corresponds to <DynamicLink href="/nqtr#onrunprops">`OnRunProps`</DynamicLink>.

    <CodeBlockTabs defaultValue="content/labels/start.label.ts">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="content/labels/start.label.ts">
          content/labels/start.label.ts
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="content/labels/start.label.ts">
        ```ts
        import { newLabel } from "@drincs/pixi-vn";
        import { aliceQuest } from "../values/quests";

        const startLabel = newLabel("start", [
            async (props) => {
                await aliceQuest.advanceIfCompleted(props); // [!code focus]
            },
        ]);
        export default startLabel;
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>
</Accordions>

Get [#get]

To get a `quest` by its `id`, use the `RegisteredQuests.get` function.

```ts
import { RegisteredQuests } from "@drincs/nqtr";

const aliceQuest = RegisteredQuests.get("aliceQuest");
```

Get all [#get-all]

To get all `quests`, use the `RegisteredQuests.values` function.

```ts
import { RegisteredQuests } from "@drincs/nqtr";

const quests = RegisteredQuests.values();
```

Custom class [#custom-class]

<Callout title="Templates" type="info">
  In all templates, the `Quest` class is already defined in the file `models/nqtr/Quest.ts`. You can use it directly or modify it to suit your needs.
</Callout>

It is recommended to create your own class `Quest` that extends `QuestStoredClass` and "override" the interface `QuestInterface` to add, edit, or remove properties or methods.

For example, if you want to create a class `Quest`, you must "override" the interface `QuestInterface` to use your properties or methods. (See the file `nqtr.d.ts`)

Now you can create a class `Quest` that extends `QuestStoredClass` and implements the `QuestInterface`. (For more information on how to create a class in TypeScript, read [the official documentation](https://www.typescriptlang.org/docs/handbook/2/classes.html))

To create a property that stores its value in the game storage, you can create [Getters/Setters](https://www.typescriptlang.org/docs/handbook/2/classes.html#getters--setters) and use the `this.getStorageProperty()` / `this.setStorageProperty()` methods. (See the file `Quest.ts`)

<CodeBlockTabs defaultValue="models/nqtr/Quest.ts">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="models/nqtr/Quest.ts">
      models/nqtr/Quest.ts
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="nqtr.d.ts">
      nqtr.d.ts
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="models/nqtr/Quest.ts">
    ```ts
    import {
        QuestInterface,
        QuestStoredClass,
        QuestStoredClassProps,
        StageInterface,
    } from "@drincs/nqtr";

    export default class Quest extends QuestStoredClass implements QuestInterface {
        constructor(
            id: string,
            _stages: StageInterface[],
            props: {
                name?: string;
                description?: string;
                image?: string;
                inDevelopment?: boolean;
            } & QuestStoredClassProps,
        ) {
            super(id, _stages, props);
            this.name = props.name || "";
            this.description = props.description || "";
            this.image = props.image;
            this.inDevelopment = props.inDevelopment || false;
        }
        readonly name: string;
        readonly description: string;
        readonly image?: string;
        readonly inDevelopment: boolean;
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="nqtr.d.ts">
    ```ts
    declare module "@drincs/nqtr" {
        interface QuestInterface {
            /**
             * The name of the quest.
             */
            readonly name: string;
            /**
             * The description of the quest.
             */
            readonly description: string;
            /**
             * The image of the quest.
             */
            readonly image?: string;
            /**
             * If the quest is in development.
             */
            readonly inDevelopment: boolean;
        }
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

FAQ [#faq]

<Accordions>
  <Accordion title="fail_quest" id="fail-quest">
    To fail a quest, you can use the `failed` property of the `QuestBaseModel` class. This property is a boolean that indicates whether the quest has been failed or not. You can set it to `true` to fail the quest.

    <CodeBlockTabs defaultValue="content/labels/start.label.ts">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="content/labels/start.label.ts">
          content/labels/start.label.ts
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="content/labels/start.label.ts">
        ```ts
        import { newLabel } from "@drincs/pixi-vn";
        import { aliceQuest } from "../values/quests";

        const startLabel = newLabel("start", [
            async (props) => {
                aliceQuest.failed = true; // [!code focus]
            },
        ]);
        export default startLabel;
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>

  <Accordion title="queries" id="queries">
    To query the state of a quest, you can use the following properties and methods:

    `currentStageIndex`: the index of the current stage of the quest. It starts at `0` and goes up to `stages.length - 1`. If the quest is completed, it will be equal to `stages.length`.

    * `started`: a boolean that indicates whether the quest has been started or not.
    * `completed`: a boolean that indicates whether the quest has been completed or not.
    * `failed`: a boolean that indicates whether the quest has been failed or not.

    <CodeBlockTabs defaultValue="Typescript">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Typescript">
          Typescript
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="ink">
          ink
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Typescript">
        ```ts  title="content/labels/start.label.ts" groupId="narrative_language"
        import { newLabel } from "@drincs/pixi-vn";
        import { aliceQuest } from "../values/quests";

        const startLabel = newLabel("start", [
            async () => {
                await showImage(BACKGROUND_ID, "alice_terrace0At");
                narration.dialogue = {
                    character: alice,
                    text: "Hi, what do you want to talk about?",
                };
                let optionsMenu: StoredChoiceInterface[] = [];
                if (aliceQuest.started) {
                    // [!code focus]
                    optionsMenu.push(newChoiceOption("Quest started", startLabel, {}));
                }
                if (aliceQuest.completed) {
                    // [!code focus]
                    optionsMenu.push(
                        newChoiceOption("Quest completed", startLabel, {}),
                    );
                }
                if (aliceQuest.failed) {
                    // [!code focus]
                    optionsMenu.push(newChoiceOption("Quest failed", startLabel, {}));
                }
                if (aliceQuest.currentStageIndex === 3) {
                    // [!code focus]
                    optionsMenu.push(
                        newChoiceOption("Quest not started", startLabel, {}),
                    );
                }
                narration.choices = [...optionsMenu, newCloseChoiceOption("Cancel")];
            },
        ]);
        export default startLabel;
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ink">
        ```ink  title="ink/start.ink" groupId="narrative_language"
        === start ===
        //  {aliceQuest} the id of the quest
        + {aliceQuest_started} Quest started
            -> start
        + {aliceQuest_completed} Quest completed
            -> start
        + {aliceQuest_failed} Quest failed
            -> start
        + {aliceQuest_currentStageIndex == 3} Quest not started
            -> start
        + Cancel
            -> DONE
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>
</Accordions>
