nqtr-navigation

Exact captured content · path skills/navigation/SKILL.md · sha256 97de8ccb900439f0… · revision 4b90054711c6a86f7f2a9eb2ae90428137bc20c1

Download the exact captured file

---
name: nqtr-navigation
description: Use when defining or navigating NQTR rooms, locations and maps — RoomBaseModel/LocationBaseModel/MapBaseModel, RegisteredRooms/RegisteredLocations/RegisteredMaps, navigator.currentRoom, "# enter room" in ink, entrance rooms, and custom Room/Location/Map classes. Navigation is the base of NQTR; activities and routines attach to it.
---

# NQTR navigation and map

Official docs: [pixi-vn.com/nqtr/navigation](https://pixi-vn.com/nqtr/navigation). Index: [nqtr](../getting-started/SKILL.md). Prefer ink for story-side navigation when the project uses ink: [nqtr-ink](../ink/SKILL.md).

## Concepts

| Element | Role |
| --- | --- |
| `room` | The core element; the position of the MC and NPCs is deduced from it |
| `location` | Container of rooms |
| `map` | Container of locations |

The player moves between rooms; the current location and map are derived from the current room. The terms are conventional (in a space game: planets/solar systems/galaxy).

## Initialize

Create map → location → room (each needs its parent) and register them. `Registered*.add` is **required**.

```ts title="content/maps.ts"
import { MapBaseModel, RegisteredMaps } from "@drincs/nqtr";

export const mainMap = new MapBaseModel("main_map", { name: "Main Map", image: "map" });
RegisteredMaps.add(mainMap);
```

```ts title="content/locations.ts"
import { LocationBaseModel, RegisteredLocations } from "@drincs/nqtr";
import { mainMap } from "@/content/maps";

export const mcHome = new LocationBaseModel("mc_home", mainMap, { name: "MC Home" });
RegisteredLocations.add(mcHome);
```

```ts title="content/rooms.ts"
import { RegisteredRooms, RoomBaseModel } from "@drincs/nqtr";
import { mcHome } from "@/content/locations";

export const mcRoom = new RoomBaseModel("mc_room", mcHome, {
    name: "MC room",
    image: "location_myroom",
    activities: ["bed"],          // see nqtr-activity
});
export const aliceRoom = new RoomBaseModel("alice_room", mcHome, {
    name: "Alice room",
    image: "location_aliceroom",
    routine: ["alice_sleep"],     // see nqtr-routine
});
RegisteredRooms.add(mcRoom, aliceRoom);
```

Import order: content files that reference each other must not form cycles; the parent (map, then location) is imported by the child.

## Get / get all

`RegisteredRooms.get(id)`, `RegisteredLocations.get(id)`, `RegisteredMaps.get(id)`; `.values()` returns all. `navigator.rooms/locations/maps` list the registered entities.

## Navigate

Set the current room; location and map update automatically. Do it at game start too.

```ink
=== start ===
# enter room mc_room
-> DONE
```

```ts
import { navigator } from "@drincs/nqtr";
navigator.currentRoom = mcRoom;            // accepts a room or its id
navigator.currentRoomId; navigator.currentLocation; navigator.currentMap;
```

`navigator.clearExpiredActivities()` drops activities whose schedule ended.

## Custom classes

Extend `RoomStoredClass` / `LocationStoredClass` / `MapStoredClass`, implement the matching interface, and augment it in `nqtr.d.ts` (`name`, `sprite`/`background`, `disabled`, `hidden`, `entrance`, ...). Constructor shapes: `Room(id, location, { activities?, routine? })`, `Location(id, map, activities?)`, `Map(id, activities?)`. Store mutable flags with `getStorageProperty`/`setStorageProperty`:

```ts title="models/Room.ts"
export default class Room extends RoomStoredClass implements RoomInterface {
    constructor(id: string, location: LocationInterface, props: { name: string; activities?: (ActivityInterface | ActivityIdType)[]; routine?: (CommitmentInterface | CommitmentIdType)[]; isEntrance?: boolean }) {
        super(id, location, { activities: props.activities || [], routine: props.routine || [] });
        this.name = props.name;
        this.isEntrance = props.isEntrance || false;
    }
    readonly name: string;
    readonly isEntrance: boolean;
    get hidden(): boolean { return this.getStorageProperty<boolean>("hidden") || false; }
    set hidden(value: boolean) { this.setStorageProperty("hidden", value); }
}
```

### Graphical elements (`background`, `sprite`, `icon`)

- `background` accepts a string (the **alias of a linked asset**) or a `TimeSlotsImage` (see [time](../time/SKILL.md)) to change the image by time of day.
- `sprite` accepts either a **string** (the alias of a linked asset) or a function returning a PixiJS element (e.g. a `Sprite` with `eventMode: "static"` and a `pointerdown` handler) when you need full control.
- `icon` is a function returning a UI element (e.g. a React button).

### Default ("entrance") room of a location

Add `isEntrance` to `Room` (and `RoomInterface`), and in `Location` expose `get entrance()` returning `super.rooms.find((r) => r.isEntrance) || super.rooms[0]` (or `undefined` if empty). A map UI then does `navigator.currentRoom = location.entrance` when the location icon is clicked.

## Pitfalls

- Forgetting `Registered*.add`: the room cannot be saved or entered from ink.
- Overriding `rooms`/`locations` getters to filter `hidden` is common; remember hidden rooms are then absent from `location.rooms`.