Skip to content

Quests

Quests are multi-objective tasks that players can claim and complete. The quest system supports type-safe data, scoped event listeners, mail, and phone call dialogs.

Basic Quest

Create a quest by extending Quest<T> with a data type parameter:

typescript
import { Quest, Network, RegisterQuest } from "@hotbunny/hackhub-content-sdk";

interface MyQuestData {
    targetIp: string;
    attempts: number;
}

@RegisterQuest
class InfiltrationQuest extends Quest<MyQuestData> {

    Name = "Infiltration";
    Title = "Server Infiltration";
    Description = "Hack into the target server.";
    Rewards = { money: 5000, xp: 200 };
    Objectives = [
        { name: "scan", description: "Scan the target server" },
        { name: "connect", description: "Connect via SSH", unlocksAfter: ["scan"] },
    ];

    CreateData(): MyQuestData {
        return {
            targetIp: Network.randomIp(),
            attempts: 0,
        };
    }

    OnStart() {
        Network.createSubnetNetwork({
            ip: this.Data.targetIp,
            type: "ROUTER",
            ports: [{ external: 22, internal: 22, active: true, service: "ssh" }],
            users: [Network.createUser({ username: "admin", password: "secret123" })],
            children: [],
        });

        this.Events.on("Terminal.NmapScan", (data) => {
            if (data.ip === this.Data.targetIp) {
                this.SetData("attempts", this.Data.attempts + 1);
                this.completeObjective("scan");
            }
        });

        this.Events.on("Terminal.SSH.Connected", (data) => {
            if (data.ip === this.Data.targetIp) this.completeObjective("connect");
        });
    }

    OnComplete() {
        Network.destroyNetwork(this.Data.targetIp);
    }

}

Quest Data

Quest data is initialized once when the quest is first claimed via CreateData(). The returned object is persisted and available as this.Data throughout the quest lifecycle.

CreateData(): T

Override this method to return the initial quest data. This is called exactly once.

typescript
CreateData(): MyQuestData {
    return {
        targetIp: Network.randomIp(),
        secretCode: Random.password(),
    };
}

this.Data

Access the quest data. Fully typed based on the generic parameter.

typescript
OnStart() {
    console.log(this.Data.targetIp);    // string
    console.log(this.Data.secretCode);  // string
}

SetData(key, value)

Update a single key in the quest data and persist it. Both key and value are type-safe.

typescript
this.SetData("attempts", this.Data.attempts + 1);  // OK
this.SetData("attempts", "wrong");                   // Compile error
this.SetData("nonexistent", 1);                      // Compile error

Scoped Events

Use this.Events instead of the global Events namespace inside quests. Listeners registered through this.Events are automatically cleaned up when the quest completes or is abandoned, preventing memory leaks.

typescript
OnStart() {
    this.Events.on("Terminal.NmapScan", (data) => {
        // automatically removed on complete/abandon
    });
}
MethodDescription
this.Events.on(event, handler)Register a listener (auto-cleaned)
this.Events.off(event, handler)Remove a specific listener
this.Events.offAll()Remove all listeners

TIP

Always prefer this.Events.on() over the global Events.on() inside quests. The global Events.on() does not auto-cleanup and can cause memory leaks.

Lifecycle Hooks

HookWhen it's called
OnStart()Quest is claimed/started. Set up event listeners here.
OnComplete()All objectives completed. Clean up resources here.
OnAbandon()Player abandons the quest.
OnObjectivesStart()Objectives listener starts (e.g. after game load).

Objectives

Objectives are defined as an array of objects:

typescript
Objectives = [
    { name: "scan", description: "Scan the target" },
    { name: "exploit", description: "Run the exploit", unlocksAfter: ["scan"] },
    { name: "download", description: "Download the files", unlocksAfter: ["exploit"], hidden: true },
];
PropertyTypeDescription
namestringUnique identifier
descriptionstringDisplayed to the player
unlocksAfterstring[]Objective names that must complete first
hiddenbooleanHidden until unlocked
hintstringHint text shown to the player
infostringAdditional info text
terminalCommandstringSuggested terminal command
triggerobjectDeclarative auto-complete trigger

Declarative Triggers

Instead of manually calling completeObjective(), you can use declarative triggers:

typescript
Objectives = [
    {
        name: "scan",
        description: "Scan the target",
        trigger: {
            event: "Terminal.NmapScan",
            condition: (data) => data.ip === this.Data.targetIp,
        },
    },
];

Manual Completion

typescript
this.completeObjective("scan");

Quest Properties

PropertyTypeDefaultDescription
NamestringrequiredUnique quest identifier
TitlestringrequiredDisplay title
DescriptionstringQuest description
IconstringQuest icon
RewardsQuestRewardsMoney, XP rewards
EmployerPartial<QuestEmployer>auto-generatedQuest giver (random if not set)
AutoStartbooleanfalseStart automatically
AutoCompletebooleantrueComplete when all objectives done
QuestsToCompletestring[]Required quests before this one
MaxClaimnumberMax times this quest can be claimed
AbandonablebooleanWhether player can abandon

Mail

Send in-game emails during quests:

typescript
Mails = [
    {
        title: "Mission Briefing",
        content: "Your target is ready. Good luck.",
        attachment: { name: "target", extension: "txt", content: "IP: 10.0.0.50" },
    },
];

OnStart() {
    this.sendMail(0);  // Send first mail
}

Dialogs

Create phone-call dialog trees:

typescript
Dialog = {
    default: [
        {
            speaker: "Handler",
            text: "Are you ready for the mission?",
            options: [
                { label: "Yes", text: "I'm ready.", switchBranch: "briefing" },
                { label: "No", text: "Not yet.", isEnd: true },
            ],
        },
    ],
    briefing: [
        { speaker: "Handler", text: "Good luck out there.", isEnd: true },
    ],
};

OnStart() {
    this.createDialog("default");
}

HotBunny Interactive Entertainment Inc.