Tour
The Tour namespace walks the player through something, without a quest to hang it on.
A quest can carry its own tour through Quest.Steps, and that is the right place for one whenever there is a quest: the steps follow the objectives and clear themselves up when the job is done. Tour is for the part before that. A pack whose content starts on a website has to explain the website first, and at that point the player has taken no job, so there is nothing in the log to attach an explanation to.
Requires the ui permission.
Available since SDK v0.23.0
The Tour namespace requires @hotbunny/[email protected] or newer. Run npm install @hotbunny/hackhub-content-sdk@latest to update.
Import
import { Tour } from "@hotbunny/hackhub-content-sdk";How it behaves
The tour follows the player rather than counting. When more than one step's target is on screen it shows the earliest, so a player who navigates between pages is always being told about the page they are on, and one who goes back gets the earlier bubble back.
A step whose target is nowhere on screen shows nothing. That makes starting a tour cheap and idempotent enough to do on every load: a tour left running across a session is invisible until the player reaches the page it is about.
Methods
Tour.start(steps, title?, onClose?)
Show a tour, replacing any the pack already had running.
content and title are localization keys, resolved in your pack's own bundle at the moment they are shown.
| Parameter | Type | Description |
|---|---|---|
steps | TourStep[] | The steps, in the order they should be reached |
title | string | Localization key for the bubble title |
onClose | () => void | The player closed the bubble |
Since a tour is normally started again on every load, write down that the player is done with it from onClose, or Skip will only last until they next open what it explains.
Tour.start([
{ target: { page: "shop.basket" }, content: "TUT.BASKET" },
{ target: { page: "shop.checkout" }, content: "TUT.CHECKOUT" },
], "TUT.TITLE", () => SaveStorage.set("tourDone", true));Tour.stop()
Take it down. Call this when whatever it was teaching has been done.
Types
TourStep
| Field | Type | Description |
|---|---|---|
target | TourTarget | What to highlight |
content | string | Localization key for the bubble text |
placement | "top" | "right" | "bottom" | "left" | "center" | Where the bubble sits |
TourTarget
What a step highlights. App names are accepted unqualified, so a pack names its own app the way it named the class.
| Target | Points at |
|---|---|
{ app: string } | The app's open window. Only exists once the app is open |
{ taskbar: string } | The app's taskbar button. Only exists once open or pinned |
{ desktopIcon: string } | The desktop shortcut, by the app's store title |
{ page: string } | An element in a page your pack serves, marked data-hh-anchor |
{ ui: TourRegion } | A named region of the desktop |
string | A raw CSS selector, for anything the above do not cover |
For a step that says "open this", { desktopIcon } is usually the one you want: an app bought from the store lands on the desktop, not on the taskbar.
{ page } points inside a website the pack serves, at an element it marked with data-hh-anchor. A served page renders in a sandboxed frame the tour cannot see into, so the name is matched against a stand-in the browser draws over the frame. Anchor names are shared across every pack, so prefix them.
<button data-hh-anchor="shop.checkout">Checkout</button>TourRegion
Regions of the desktop are named rather than selected: "taskbar", "startMenu", "tray", "objectives", "desktop", "wifi", "controlBar".
A pack that hardcoded .task-manager-toggle would break the first time the game renamed a class, and it would have had to read the game's source to learn the name in the first place. These names are the supported surface; the selectors behind them are not.
Full example
import { Tour, SaveStorage, Localization } from "@hotbunny/hackhub-content-sdk";
Localization.registerAll({
en: {
"TUT.TITLE": "Getting paid",
"TUT.BASKET": "Everything you picked up lands here.",
"TUT.CHECKOUT": "Pay here, and the order shows up in your mail.",
},
});
// Safe to call on every load: invisible until the player opens the shop.
if (!SaveStorage.get<boolean>("tourDone")) {
Tour.start([
{ target: { page: "shop.basket" }, content: "TUT.BASKET" },
{ target: { page: "shop.checkout" }, content: "TUT.CHECKOUT" },
], "TUT.TITLE", () => SaveStorage.set("tourDone", true));
}