• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

WolferyScripting / WolferyJS / #14

29 Aug 2025 02:35AM UTC coverage: 59.833% (+0.6%) from 59.188%
#14

push

DonovanDMC
0.0.5

92 of 246 branches covered (37.4%)

Branch coverage included in aggregate %.

4995 of 8256 relevant lines covered (60.5%)

1.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

4.01
/lib/models/ControlledCharacter.ts
1
import type Room from "./Room.js";
1✔
2
import type Area from "./Area.js";
1✔
3
import type Profile from "./Profile.js";
1✔
4
import BaseModel from "./BaseModel.js";
1✔
5
import type Character from "./Character.js";
1✔
6
import type Exit from "./Exit.js";
1✔
7
import type RoomProfile from "./RoomProfile.js";
1✔
8
import type RoomScript from "./RoomScript.js";
1✔
9
import type OwnedCharacter from "./OwnedCharacter.js";
1✔
10
import type Puppet from "./Puppet.js";
1✔
11
import type RoomCharacter from "./RoomCharacter.js";
1✔
12
import type RoomDetails from "./RoomDetails.js";
1✔
13
import type LookedAt from "./LookedAt.js";
1✔
14
import type FocusChars from "./FocusChars.js";
1✔
15
import type HiddenExits from "./HiddenExits.js";
1✔
16
import type AreaDetails from "./AreaDetails.js";
1✔
17
import type AreaChild from "./AreaChild.js";
1✔
18
import type RoomChild from "./RoomChild.js";
1✔
19
import ResourceIDs from "../generated/ResourceIDs.js";
1✔
20
import {
1✔
21
    type KeyBasicResponse,
1✔
22
    type BasicCharacterResponse,
1✔
23
    type NameBasicResponse,
1✔
24
    type DeleteNameResponse,
1✔
25
    type Messages,
1✔
26
    type PublicPopulationUpdate,
1✔
27
    type AreaDetailsPopulationUpdate
1✔
28
} from "../util/types.js";
1✔
29
import type WolferyJS from "../WolferyJS.js";
1✔
30
import type Commands from "../util/commands.js";
1✔
31
import ResEventObserver from "../util/ResEventObserver.js";
1✔
32
import type { ControlledCharacterProperties } from "../generated/models/types.js";
1✔
33
import { ControlledCharacterDefinition } from "../generated/models/definitions.js";
1✔
34
import { PING_DURATION } from "../util/Constants.js";
1✔
35
import type RoomProfiles from "../collections/RoomProfiles.js";
1✔
36
import type RoomScripts from "../collections/RoomScripts.js";
1✔
37
import { kControlledCharacter } from "../util/Util.js";
1✔
38
import { fileTypeFromBuffer } from "file-type";
1✔
39
import { ResRef, type ResClient } from "resclient-ts";
1✔
40

1✔
41
declare interface ControlledCharacter extends BaseModel, ControlledCharacterProperties {}
1✔
42
// do not edit the first line of the class comment
1✔
43
/**
1✔
44
 * A controlled character.
1✔
45
 * @resourceID {@link ResourceIDs.CONTROLLED_CHARACTER | CONTROLLED_CHARACTER}
1✔
46
 * @resourceID {@link ResourceIDs.CONTROLLED_PUPPET | CONTROLLED_PUPPET}
1✔
47
 */
1✔
48
class ControlledCharacter extends BaseModel implements ControlledCharacterProperties {
×
49
    private _focusChars!: FocusChars | null;
×
50
    private _hiddenExits!: HiddenExits | null;
×
51
    private _pingTimeout!: NodeJS.Timeout | null;
×
52
    private _roomProfiles!: RoomProfiles | null;
×
53
    private _roomScripts!: RoomScripts | null;
×
54
    private onAreaChildChange = this._onAreaChildChange.bind(this);
×
55
    private onAreaDetailsChange = this._onAreaDetailsChange.bind(this);
×
56
    private onChange = this._onChange.bind(this);
×
57
    private onExitChange = this._onExitChange.bind(this);
×
58
    private onOut = this._onOut.bind(this);
×
59
    private onRoomChildChange = this._onRoomChildChange.bind(this);
×
60
    private onRoomDetailsChange = this._onRoomDetailsChange.bind(this);
×
61
    constructor(client: WolferyJS, api: ResClient, rid: string) {
×
62
        super(client, api, rid, { definition: ControlledCharacterDefinition });
×
63
        this.p
×
64
            .writable("_focusChars", null)
×
65
            .writable("_hiddenExits", null)
×
66
            .writable("_pingTimeout", null)
×
67
            .writable("_roomProfiles", null)
×
68
            .writable("_roomScripts", null)
×
69
            .writable("onAreaChildChange", null)
×
70
            .writable("onAreaDetailsChange")
×
71
            .writable("onChange")
×
72
            .writable("onExitChange")
×
73
            .writable("onOut")
×
74
            .writable("onRoomChildChange", null)
×
75
            .writable("onRoomDetailsChange");
×
76
    }
×
77

×
78
    private async _onAreaChildChange(data: Partial<AreaChild>, area: AreaChild): Promise<void> {
×
79
        if (data.pop !== undefined) {
×
80
            const update: PublicPopulationUpdate = {
×
81
                public:    area.pop,
×
82
                publicOld: data.pop ?? area.pop
×
83
            };
×
84
            this.client.emit("area.child.populationChange", this, area, update);
×
85
        }
×
86
    }
×
87

×
88
    private async _onAreaDetailsChange(data: Partial<AreaDetails>, area: AreaDetails): Promise<void> {
×
89
        if (data.pop !== undefined || data.prv !== undefined) {
×
90
            const update: AreaDetailsPopulationUpdate = {
×
91
                private:    area.prv,
×
92
                privateOld: data.prv ?? area.prv,
×
93
                public:     area.pop,
×
94
                publicOld:  data.pop ?? area.pop
×
95
            };
×
96
            this.client.emit("area.details.populationChange", this, area, update);
×
97
        }
×
98
    }
×
99

×
100
    // make sure to update trackChanges in _listen if anything is added here
×
101
    private async _onChange(data: Partial<this>): Promise<void> {
×
102
        if (this.client.anyTracked("lookAt") && data.lookingAt !== undefined) {
×
103
            this.client.emit("lookAtChange", this, this.lookingAt?.char ?? null, data.lookingAt?.char ?? null);
×
104
        }
×
105

×
106
        if (this.client.anyTracked("lookedAt") && data.lookedAt !== undefined) {
×
107
            this._listenLookedAt(false, data.lookedAt);
×
108
            this._listenLookedAt(true, this.lookedAt);
×
109
        }
×
110

×
111
        if (this.client.anyTracked("roomChange") && data.inRoom !== undefined) {
×
112
            await this._listenRoom(false, data.inRoom);
×
113
            await this._listenRoom(true, this.inRoom);
×
114
            this.client.emit("roomChange.details", this, this.inRoom, data.inRoom);
×
115
        }
×
116
    }
×
117

×
118
    // make sure to update trackRoomChanges in _listen if anything changes here
×
119
    private _onExitChange(data: Partial<Exit>, exit: Exit): void {
×
120
        // this.client.emit("exits.change", this, this.inRoom, exit, data);
×
121
        if (data.target !== undefined) {
×
122
            if (data.target === null) {
×
123
                this._listenExit(false, exit);
×
124
                this._listenExit(true, exit);
×
125
            } else if (this.client.anyTracked("roomCharactersExit") && data.target.awake) {
×
126
                this.listeners.remove(data.target.awake, kControlledCharacter(this.id));
×
127
            }
×
128
        }
×
129
    }
×
130

×
131
    private async _onOut(data: Messages.Any): Promise<void> {
×
132
        const sent = "char" in data && this.id === data.char.id;
×
133

×
134
        if (data.type === "broadcast") return; // do characters even get these?
×
135

×
136
        if (data.type === "privateDescribe") {
×
137
            const target = await this.api.get<Character>(ResourceIDs.CHARACTER({ id: data.target.id }));
×
138
            this.client.emit("message", "privateDescribe", sent, this, data.msg, target, data.script);
×
139
            return;
×
140
        }
×
141

×
142
        if (data.type === "info") {
×
143
            this.client.emit("message", "info", sent, this, data.msg);
×
144
            return;
×
145
        }
×
146

×
147
        const char = await this.api.get<Character>(ResourceIDs.CHARACTER({ id: data.char.id }));
×
148

×
149
        switch (data.type) {
×
150
            case "wakeup":
×
151
            case "sleep":
×
152
            case "leave":
×
153
            case "arrive": {
×
154
                this.client.emit("message", data.type, sent, this, char, data.msg, data.method);
×
155
                break;
×
156
            }
×
157

×
158
            case "travel": {
×
159
                this.client.emit("message", "travel", sent, this, char, data.msg, data.targetRoom, data.method);
×
160
                break;
×
161
            }
×
162

×
163
            case "say":
×
164
            case "pose": {
×
165
                this.client.emit("message", data.type, sent, this, char, data.msg);
×
166
                break;
×
167
            }
×
168

×
169
            case "ooc": {
×
170
                this.client.emit("message", data.type, sent, this, char, data.msg, !!data.pose);
×
171
                break;
×
172
            }
×
173

×
174
            case "address":
×
175
            case "whisper":
×
176
            case "message":
×
177
            case "mail": {
×
178
                const target = await this.api.get<Character>(ResourceIDs.CHARACTER({ id: data.target.id }));
×
179
                this.client.emit("message", data.type, sent, this, char, data.msg, target, !!data.pose, !!data.ooc);
×
180
                break;
×
181
            }
×
182

×
183
            case "action":
×
184
            case "describe": {
×
185
                this.client.emit("message", data.type, sent, this, char, data.msg);
×
186
                break;
×
187
            }
×
188

×
189
            case "roll": {
×
190
                this.client.emit("message", "roll", sent, this, char, data.total, data.result, !!data.quiet);
×
191
                break;
×
192
            }
×
193
        }
×
194
    }
×
195

×
196
    private async _onRoomChildChange(data: Partial<RoomChild>, room: RoomChild): Promise<void> {
×
197
        if (data.pop !== undefined) {
×
198
            const update: PublicPopulationUpdate = {
×
199
                public:    room.pop,
×
200
                publicOld: data.pop
×
201
            };
×
202
            this.client.emit("room.child.populationChange", this, room, update);
×
203
        }
×
204
    }
×
205

×
206
    private async _onRoomDetailsChange(data: Partial<RoomDetails>, room: RoomDetails): Promise<void> {
×
207
        if (data.pop !== undefined) {
×
208
            const update: PublicPopulationUpdate = {
×
209
                public:    room.pop,
×
210
                publicOld: data.pop
×
211
            };
×
212
            this.client.emit("room.details.populationChange", this, room, update);
×
213
        }
×
214
    }
×
215

×
216
    protected override async _listen(on: boolean): Promise<void> {
×
217
        await super._listen(on);
×
218
        const m = on ? "resourceOn" : "resourceOff";
×
219
        // don't listen to changes if we aren't looking for anything
×
220
        const trackChanges = this.client.anyTracked("lookAt", "lookedAt", "roomChange");
×
221
        const trackRoomChanges = this.client.anyTracked("hiddenExits", "roomProfiles", "roomScripts", "exits", "population", "roomCommands", "roomCharacters", "roomCharactersExit", "roomProfiles", "roomScripts");
×
222
        if (trackChanges) this[m]("change", this.onChange);
×
223
        if (this.client.anyTracked("messages")) this[m]("out", this.onOut);
×
224
        if (this.client.options.pingCharacters) {
×
225
            if (on) {
×
226
                this._pingTimeout = setInterval(() => this.ping(),  PING_DURATION);
×
227
            } else if (this._pingTimeout) {
×
228
                clearInterval(this._pingTimeout);
×
229
            }
×
230
        }
×
231

×
232
        if (on) {
×
233
            // eslint-disable-next-line unicorn/no-lonely-if
×
234
            if (this.client.anyTracked("focusChars") && this.client.anyTracked("focus")) this._focusChars = await this.settings.focus.getChars();
×
235
        }
×
236

×
237
        if (this.client.anyTracked("profiles")) {
×
238
            this.listeners.addOrRemove(on, this.profiles, data => this.client.emit("profiles.add", this, data.item), data => this.client.emit("profiles.remove", this, data.item), kControlledCharacter(this.id));
×
239
        }
×
240
        if (this.client.anyTracked("ownedAreas")) {
×
241
            this.listeners.addOrRemove(on, this.ownedAreas, data => this.client.emit("ownedAreas.add", this, data.item), data => this.client.emit("ownedAreas.remove", this, data.item), kControlledCharacter(this.id));
×
242
        }
×
243
        if (this.client.anyTracked("ownedRooms")) {
×
244
            this.listeners.addOrRemove(on, this.ownedRooms, data => this.client.emit("ownedRooms.add", this, data.item), data => this.client.emit("ownedRooms.remove", this, data.item), kControlledCharacter(this.id));
×
245
        }
×
246
        if (this.client.anyTracked("characterNodes")) {
×
247
            this.listeners.addOrRemove(on, this.nodes, data => this.client.emit("characterNodes.add", this, data.item), data => this.client.emit("characterNodes.remove", this, data.item), kControlledCharacter(this.id));
×
248
        }
×
249
        if (this.client.anyTracked("focus")) {
×
250
            this.listeners.addOrRemove(on, this.settings.focus, data => this.client.emit("focus.add", this, data.item, new ResRef(this.api, ResourceIDs.CHARACTER({ id: data.key }))), data => this.client.emit("focus.remove", this, data.item, new ResRef(this.api, ResourceIDs.CHARACTER({ id: data.key }))), kControlledCharacter(this.id));
×
251
            if (this.client.anyTracked("focusChars") && this._focusChars) {
×
252
                this.listeners.addOrRemove(on, this._focusChars, data => this.client.emit("focusChars.add", this, data.item, this.settings.focus.props[data.item.id]!), data => this.client.emit("focusChars.remove", this, data.item, this.settings.focus.props[data.item.id]!), kControlledCharacter(this.id));
×
253
            }
×
254
        }
×
255

×
256
        if (!on) {
×
257
            this._focusChars = null;
×
258
        }
×
259

×
260
        if (this.client.anyTracked("lookedAt")) this._listenLookedAt(on, this.lookedAt);
×
261
        if (trackRoomChanges) await this._listenRoom(on, this.inRoom);
×
262
    }
×
263

×
264
    // make sure to update trackRoomChanges in _listen if anything changes here
×
265
    protected _listenExit(on: boolean, exit: Exit): void {
×
266
        const m = on ? "resourceOn" : "resourceOff";
×
267
        exit[m]("change", this.onExitChange);
×
268
        if (!exit.target?.awake || !this.client.anyTracked("roomCharactersExit")) return;
×
269
        this.listeners.addOrRemove(on, exit.target.awake, data => this.client.emit("roomCharacters.exit.add", this, exit.target!, exit, data.item), data => this.client.emit("roomCharacters.exit.remove", this, exit.target!, exit, data.item), kControlledCharacter(this.id));
×
270
    }
×
271

×
272
    protected _listenLookedAt(on: boolean, lookedAt: LookedAt): void {
×
273
        this.listeners.addOrRemove(on, lookedAt, async data => {
×
274
            const char = await this.api.get<Character>(ResourceIDs.CHARACTER({ id: data.key }));
×
275
            this.client.emit("lookedAt.add", this, char);
×
276
        }, async data => {
×
277
            const char = await this.api.get<Character>(ResourceIDs.CHARACTER({ id: data.key }));
×
278
            this.client.emit("lookedAt.remove", this, char);
×
279
        }, kControlledCharacter(this.id));
×
280
    }
×
281

×
282
    // make sure to update trackRoomChanges in _listen if anything changes here
×
283
    protected async _listenRoom(on: boolean, room: RoomDetails): Promise<void> {
×
284
        const m = on ? "resourceOn" : "resourceOff";
×
285
        const owner = room.owner.id === this.id;
×
286
        if (on) {
×
287
            if (this.client.anyTracked("hiddenExits") && owner) this._hiddenExits = await room.getHiddenExits();
×
288
            if (this.client.anyTracked("roomProfiles") && owner) this._roomProfiles = await room.getProfiles();
×
289
            if (this.client.anyTracked("roomScripts") && owner) this._roomScripts = await room.getScripts();
×
290
        }
×
291

×
292
        if (this.client.anyTracked("exits")) {
×
293
            for (const exit of room.exits) {
×
294
                this._listenExit(on, exit);
×
295
            }
×
296
        }
×
297

×
298
        if (this.client.anyTracked("population")) {
×
299
            room[m]("change", this.onRoomDetailsChange);
×
300
            if (room.area) {
×
301
                room.area[m]("change", this.onAreaDetailsChange);
×
302
                for (const child of room.area.children.areas) {
×
303
                    child[m]("change", this.onAreaChildChange);
×
304
                }
×
305
                for (const child of room.area.children.rooms) {
×
306
                    child[m]("change", this.onRoomChildChange);
×
307
                }
×
308
            }
×
309
        }
×
310
        if (this.client.anyTracked("exits")) {
×
311
            this.listeners.addOrRemove(on, room.exits, data => this.client.emit("exits.add", this, room, data.item), data => this.client.emit("exits.remove", this, room, data.item), kControlledCharacter(this.id));
×
312
        }
×
313
        if (this.client.anyTracked("roomCommands")) {
×
314
            this.listeners.addOrRemove(on, room.cmds, data => this.client.emit("roomCommands.add", this, room, data.item), data => this.client.emit("roomCommands.remove", this, room, data.item), kControlledCharacter(this.id));
×
315
        }
×
316
        if (this.client.anyTracked("roomCharacters") && room.chars) {
×
317
            this.listeners.addOrRemove(on, room.chars, data => this.client.emit("roomCharacters.add", this, room, data.item), data => this.client.emit("roomCharacters.remove", this, room, data.item), kControlledCharacter(this.id));
×
318
        }
×
319
        if (this.client.anyTracked("hiddenExits") && owner && this._hiddenExits) {
×
320
            this.listeners.addOrRemove(on, this._hiddenExits, data => this.client.emit("exits.hidden.add", this, room, data.item), data => this.client.emit("exits.hidden.remove", this, room, data.item), kControlledCharacter(this.id));
×
321
            for (const exit of this._hiddenExits) {
×
322
                this._listenExit(on, exit);
×
323
            }
×
324
        }
×
325
        if (this.client.anyTracked("roomProfiles") && owner && this._roomProfiles) {
×
326
            this.listeners.addOrRemove(on, this._roomProfiles, data => this.client.emit("roomProfiles.add", this, room, data.item), data => this.client.emit("roomProfiles.remove", this, room, data.item), kControlledCharacter(this.id));
×
327
        }
×
328
        if (this.client.anyTracked("roomScripts") && owner && this._roomScripts) {
×
329
            this.listeners.addOrRemove(on, this._roomScripts, data => this.client.emit("roomScripts.add", this, room, data.item), data => this.client.emit("roomScripts.remove", this, room, data.item), kControlledCharacter(this.id));
×
330
        }
×
331

×
332
        if (!on) {
×
333
            this._hiddenExits = null;
×
334
            this._roomProfiles = null;
×
335
            this._roomScripts = null;
×
336
        }
×
337
    }
×
338

×
339
    get avatarURL(): string | null {
×
340
        return this.avatar === "" ? null : `${this.client.fileURL}/core/char/avatar/${this.avatar}`;
×
341
    }
×
342

×
343
    get fullname(): string {
×
344
        return `${this.name} ${this.surname}`.trim();
×
345
    }
×
346

×
347
    get isPuppet(): boolean {
×
348
        return ResourceIDs.CONTROLLED_PUPPET.regex.test(this.rid);
×
349
    }
×
350

×
351
    /**
×
352
     * Accept a control request for a character.
×
353
     * @param charId The ID of the character to accept control for.
×
354
     */
×
355
    async acceptControl(charId: string): Promise<null> {
×
356
        return this.call<null>("controlRequestAccept", { charId });
×
357
    }
×
358

×
359
    /**
×
360
     * Add a tag to this character.
×
361
     * @param tagId The ID of the tag to add.
×
362
     * @param pref The preference for the tag.
×
363
     */
×
364
    async addTag(tagId: string, pref: "like" | "dislike"): Promise<null> {
×
365
        return this.tags.add(tagId, pref);
×
366
    }
×
367

×
368
    /**
×
369
     * Add a new teleport node.
×
370
     * @param options The options for the teleport node.
×
371
     */
×
372
    async addTeleport(options: Commands.ControlledCharacter.AddTeleportOptions): Promise<null> {
×
373
        return this.call<null>("addTeleport", options);
×
374
    }
×
375

×
376
    /**
×
377
     * Address a character. This is the equivalent of the `@` command in the web client.
×
378
     * @param charId The ID of the character to address.
×
379
     * @param options The options for addressing the character.
×
380
     */
×
381
    async address(charId: string, options: Commands.ControlledCharacter.AddressOptions): Promise<null> {
×
382
        return this.call<null>("address", { charId, ...options });
×
383
    }
×
384

×
385
    /** Set this character as away (afk). */
×
386
    async away(status?: string): Promise<null> {
×
387
        return this.call<null>("away", { status });
×
388
    }
×
389

×
390
    /**
×
391
     * Copy this character's avatar to the profile.
×
392
     * @param profileId The ID of the profile to copy the avatar to.
×
393
     * @returns The profile.
×
394
     */
×
395
    async copyProfileAvatar(profileId: string): Promise<Profile> {
×
396
        return this.call<Record<"profile", NameBasicResponse>>("copyProfileAvatar", { profileId })
×
397
            .then(r => this.api.get<Profile>(ResourceIDs.PROFILE({ id: r.profile.id })));
×
398
    }
×
399

×
400
    /**
×
401
     * Copy this character's image to the profile.
×
402
     * @param profileId The ID of the profile to copy the image to.
×
403
     * @returns The profile.
×
404
     */
×
405
    async copyProfileImage(profileId: string): Promise<Profile> {
×
406
        return this.call<Record<"profile", NameBasicResponse>>("copyProfileImage", { profileId })
×
407
            .then(r => this.api.get<Profile>(ResourceIDs.PROFILE({ id: r.profile.id })));
×
408
    }
×
409

×
410
    /**
×
411
     * Create a new area.
×
412
     * @param name The name of the area.
×
413
     */
×
414
    async createArea(name: string): Promise<Area> {
×
415
        return this.call<NameBasicResponse>("createArea", { name })
×
416
            .then(r => this.client.waitForCached<Area>(ResourceIDs.AREA({ id: r.id })));
×
417
    }
×
418

×
419
    /**
×
420
     * Create an exit to another room.
×
421
     * @param name The name of the exit.
×
422
     * @param keys The keys to use to go through the exit.
×
423
     * @param targetRoom The ID of the room to go to. Provide `null` to create a new room.
×
424
     */
×
425
    async createExit(name: string, keys: Array<string>, targetRoom: string | null): Promise<{ exit: Exit; targetRoom: Room; }> {
×
426
        return this.call<Record<"exit" | "targetRoom", NameBasicResponse>>("createExit", { name, keys, targetRoom }).then(async r => {
×
427
            const [exit, room] = await Promise.all([this.client.waitForCached<Exit>(ResourceIDs.EXIT({ id: r.exit.id })), this.client.waitForCached<Room>(ResourceIDs.ROOM({ id: r.targetRoom.id }))]);
×
428
            return { exit, targetRoom: room };
×
429
        });
×
430
    }
×
431

×
432
    /**
×
433
     * Create a profile based on this character's current attributes.
×
434
     * @param name The name of the profile.
×
435
     * @param key The key of the profile.
×
436
     */
×
437
    async createProfile(name: string, key: string): Promise<Profile> {
×
438
        return this.call<Record<"profile", NameBasicResponse>>("createProfile", { name, key }).then(r => this.client.waitForCached<Profile>(ResourceIDs.PROFILE({ id: r.profile.id })));
×
439
    }
×
440

×
441
    /**
×
442
     * Create a room.
×
443
     * @param name The name of the room.
×
444
     */
×
445
    async createRoom(name: string): Promise<Room> {
×
446
        return this.call<NameBasicResponse>("createRoom", { name }).then(r => this.client.waitForCached<Room>(ResourceIDs.ROOM({ id: r.id })));
×
447
    }
×
448

×
449
    /**
×
450
     * Create a room profile. You must own the room, and be present in it.
×
451
     * @param name The name of the room profile.
×
452
     * @param key The key of the room profile.
×
453
     */
×
454
    async createRoomProfile(name: string, key: string): Promise<RoomProfile> {
×
455
        return this.call<Record<"profile", NameBasicResponse>>("createRoomProfile", { name, key }).then(r => this.client.waitForCached<RoomProfile>(ResourceIDs.ROOM_PROFILE({ id: r.profile.id })));
×
456
    }
×
457

×
458
    /**
×
459
     * Create a room script. It will be created in the room the character is located in.
×
460
     * @param key The key of the room script.
×
461
     * @param options The options for the room script.
×
462
     */
×
463
    async createRoomScript(key: string, options?: Commands.ControlledCharacter.CreateRoomScriptOptions): Promise<{ room: Room; script: RoomScript; }> {
×
464
        return this.call<{ room: NameBasicResponse; script: KeyBasicResponse; }>("createRoomScript", { key, ...options }).then(async r => {
×
465
            const [room, script] = await Promise.all([
×
466
                this.api.get<Room>(ResourceIDs.ROOM({ id: r.room.id })),
×
467
                this.api.get<RoomScript>(ResourceIDs.ROOMSCRIPT({ id: r.script.id }))
×
468
            ]);
×
469
            return { room, script };
×
470
        });
×
471
    }
×
472

×
473
    /**
×
474
     * Delete an area.
×
475
     * @param areaId The ID of the area to delete.
×
476
     */
×
477
    async deleteArea(areaId: string): Promise<{ area: Area; response: DeleteNameResponse; }> {
×
478
        const area = await this.api.get<Area>(ResourceIDs.AREA({ id: areaId }));
×
479
        return this.call<DeleteNameResponse>("deleteArea", { areaId })
×
480
            .then(r => ({ area, response: r }));
×
481
    }
×
482

×
483
    /**
×
484
     * Delete an exit.
×
485
     * @param exitId The ID of the exit to delete.
×
486
     */
×
487
    async deleteExit(exitId: string): Promise<Exit> {
×
488
        const exit = this.api.get<Exit>(ResourceIDs.EXIT({ id: exitId }));
×
489
        return this.call<null>("deleteExit", { exitId }).then(() => exit);
×
490
    }
×
491

×
492
    /**
×
493
     * Delete a mail message. You must be using the username/password authentication method to use this.
×
494
     * @param messageId The ID of the message to delete.
×
495
     */
×
496
    async deleteMail(messageId: string): Promise<null> {
×
497
        return this.client.modules.core.getPlayer().then(player =>
×
498
            this.api.call<null>(ResourceIDs.PLAYER_MAIL_MESSAGE({ player: player.id, message: messageId }), "delete")
×
499
        );
×
500
    }
×
501

×
502
    /**
×
503
     * Delete a profile.
×
504
     * @param profileId The ID of the profile to delete.
×
505
     */
×
506
    async deleteProfile(profileId: string): Promise<Profile> {
×
507
        const profile = await this.api.get<Profile>(ResourceIDs.PROFILE({ id: profileId }));
×
508
        return this.call<Record<"profile", NameBasicResponse>>("deleteProfile", { profileId })
×
509
            .then(() => profile);
×
510
    }
×
511

×
512
    /**
×
513
     * Delete a room.
×
514
     * @param roomId The ID of the room to delete.
×
515
     */
×
516
    async deleteRoom(roomId: string): Promise<Room> {
×
517
        const room = await this.api.get<Room>(ResourceIDs.ROOM({ id: roomId }));
×
518
        return this.call<null>("deleteRoom", { roomId })
×
519
            .then(() => room);
×
520
    }
×
521

×
522
    /**
×
523
     * Delete a room profile. You must own the room, and be present in it.
×
524
     * @param profileId The ID of the room profile to delete.
×
525
     */
×
526
    async deleteRoomProfile(profileId: string): Promise<Record<"profile", NameBasicResponse>> {
×
527
        return this.call<Record<"profile", NameBasicResponse>>("deleteRoomProfile", { profileId });
×
528
    }
×
529

×
530
    /**
×
531
     * Delete a room script. You must own the room, and be present in it.
×
532
     * @param scriptId The ID of the room script to delete.
×
533
     */
×
534
    async deleteRoomScript(scriptId: string): Promise<{ room: Room; script: RoomScript | KeyBasicResponse; }> {
×
535
        return this.call<{ room: NameBasicResponse; script: KeyBasicResponse; }>("deleteRoomScript", { scriptId }).then(async r => {
×
536
            const room = await this.api.get<Room>(ResourceIDs.ROOM({ id: r.room.id }));
×
537
            const script = this.api.getCached<RoomScript>(ResourceIDs.ROOMSCRIPT({ id: r.script.id })) ?? r.script;
×
538
            return { room, script };
×
539
        });
×
540
    }
×
541

×
542
    /**
×
543
     * Describe a scene or action.
×
544
     * @param msg The message.
×
545
     */
×
546
    async describe(msg: string): Promise<null> {
×
547
        return this.call<null>("describe", { msg });
×
548
    }
×
549

×
550
    /**
×
551
     * Evict a character from their teleport nodes or home for the current room.
×
552
     * @param charId The ID of the character to evict.
×
553
     * @returns The evicted character.
×
554
     */
×
555
    async evict(charId: string): Promise<Character> {
×
556
        return this.call<BasicCharacterResponse<"targetChar">>("evict", { charId })
×
557
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.targetChar.id })));
×
558
    }
×
559

×
560
    /**
×
561
     * Evict a character from their home in the current room.
×
562
     * @param charId The ID of the character to evict.
×
563
     * @returns The evicted character.
×
564
     */
×
565
    async evictHome(charId: string): Promise<Character> {
×
566
        return this.call<BasicCharacterResponse<"char">>("evictHome", { charId })
×
567
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.char.id })));
×
568
    }
×
569

×
570
    /**
×
571
     * Evict a character from controlling a puppet.
×
572
     * @param charId The ID of the character to evict.
×
573
     * @param puppetId The ID of the puppet to evict.
×
574
     * @returns The evicted character and puppet.
×
575
     */
×
576
    async evictPuppeteer(charId: string, puppetId: string): Promise<{ char: Character; puppet: Character; }> {
×
577
        return this.call<BasicCharacterResponse<"char"> & BasicCharacterResponse<"puppet">>("evictPuppeteer", { charId, puppetId })
×
578
            .then(async r => {
×
579
                const [char, puppet] = await Promise.all([
×
580
                    this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.char.id })),
×
581
                    this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.puppet.id }))
×
582
                ]);
×
583
                return { char, puppet };
×
584
            });
×
585
    }
×
586

×
587
    /**
×
588
     * Evict a character from their teleport node.
×
589
     * @param charId The ID of the character to evict.
×
590
     * @returns The evicted character.
×
591
     */
×
592
    async evictTeleport(charId: string): Promise<Character> {
×
593
        return this.call<BasicCharacterResponse<"char">>("evictTeleport", { charId })
×
594
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.char.id })));
×
595
    }
×
596

×
597
    /**
×
598
     * Focus a character.
×
599
     * Focus a character.
×
600
     * @param targetId The ID of the character to focus.
×
601
     * @param options The options for focusing the character.
×
602
     * @returns The focused character.
×
603
     */
×
604
    async focusChar(targetId: string, options: Omit<Commands.Player.FocusCharOptions, "targetId">): Promise<Character> {
×
605
        return this.client.modules.core.getPlayer().then(player =>
×
606
            player.focusChar(this.id, { targetId, ...options })
×
607
        );
×
608
    }
×
609

×
610
    /**
×
611
     * Request to follow a character.
×
612
     * @param charId The ID of the character to follow.
×
613
     */
×
614
    async follow(charId: string): Promise<null> {
×
615
        return this.call<null>("follow", { charId });
×
616
    }
×
617

×
618
    /**
×
619
     * Get the character for this controlled character.
×
620
     * @returns The character.
×
621
     */
×
622
    async getChar(): Promise<Character> {
×
623
        return this.api.get<Character>(ResourceIDs.CHARACTER({ id: this.id }));
×
624
    }
×
625

×
626
    /**
×
627
     * Get an exit in the current room.
×
628
     * @param options The options for getting the exit.
×
629
     * @returns The exit.
×
630
     */
×
631
    async getExit(options: Commands.ControlledCharacter.GetExitOptions): Promise<Exit> {
×
632
        return this.call<Exit>("getExit", options);
×
633
    }
×
634

×
635
    async getLogEvents(startTime?: number): Promise<Commands.LogEvents> {
×
636
        return this.api.call<Commands.LogEvents>("log.events", "get", { charId: this.id, startTime });
×
637
    }
×
638

×
639
    /**
×
640
     * Get the owned character for this controlled character. If the character is a puppet, use {@link getPuppet} instead.
×
641
     * @returns The owned character.
×
642
     */
×
643
    async getOwnedChar(): Promise<OwnedCharacter> {
×
644
        return this.api.get<OwnedCharacter>(ResourceIDs.OWNED_CHARACTER({ id: this.id }));
×
645
    }
×
646

×
647
    /**
×
648
     * Get the puppet for this controlled character. If the character is not a puppet, use {@link getOwnedChar} instead.
×
649
     * @returns The controlled puppet.
×
650
     */
×
651
    async getPuppet(): Promise<Puppet> {
×
652
        const { char, puppet } = ResourceIDs.CONTROLLED_PUPPET.parts(this.rid);
×
653
        return this.api.get<Puppet>(ResourceIDs.PUPPET({ char, puppet }));
×
654
    }
×
655

×
656
    /**
×
657
     * Get the room character for this controlled character.
×
658
     * @returns The room character.
×
659
     */
×
660
    async getRoomChar(): Promise<RoomCharacter> {
×
661
        return this.api.get<RoomCharacter>(ResourceIDs.ROOM_CHARACTER({ id: this.id }));
×
662
    }
×
663

×
664
    /**
×
665
     * Send a message to the helpers.
×
666
     * @param msg The message.
×
667
     */
×
668
    async helpme(msg: string): Promise<null> {
×
669
        return this.call<null>("helpme", { msg });
×
670
    }
×
671

×
672
    /**
×
673
     * Request to join a character.
×
674
     * @param charId The ID of the character to join.
×
675
     */
×
676
    async join(charId: string): Promise<null> {
×
677
        return this.call<null>("join", { charId });
×
678
    }
×
679

×
680
    /**
×
681
     * Request to lead a character.
×
682
     * @param charId The ID of the character to lead.
×
683
     */
×
684
    async lead(charId: string): Promise<null> {
×
685
        return this.call<null>("lead", { charId });
×
686
    }
×
687

×
688
    /**
×
689
     * Set the LFRP status for the character.
×
690
     * @param msg The message to display when LFRP is enabled.
×
691
     */
×
692
    async lfrp(msg?: string): Promise<null> {
×
693
        if (msg) {
×
694
            await this.client.modules.core.getPlayer().then(player => player.setCharSettings(this.id, { lfrpDesc: msg }));
×
695
        }
×
696

×
697
        return this.call<null>("set", { rp: "lfrp" });
×
698
    }
×
699

×
700

×
701
    /**
×
702
     * Look at a character.
×
703
     * @param charId The ID of the character to look at.
×
704
     */
×
705
    async look(charId: string): Promise<null> {
×
706
        return this.call<null>("look", { charId });
×
707
    }
×
708

×
709
    /**
×
710
     * Send a mail to a character. You must be using the username/password authentication method to use this.
×
711
     * @param toCharId The ID of the character to send the mail to.
×
712
     * @param options The options for the mail.
×
713
     */
×
714
    async mail(toCharId: string, options: Commands.Inbox.SendOptions): Promise<Character> {
×
715
        return this.client.modules.core.getPlayer().then(player => player.getInbox()).then(inbox => inbox.send(this.id, toCharId, options));
×
716
    }
×
717

×
718
    /**
×
719
     * Send a message to a character.
×
720
     * @param charId The ID of the character to send the message to.
×
721
     * @param options The options for sending the message.
×
722
     */
×
723
    async message(charId: string, options: Commands.ControlledCharacter.MessageOptions): Promise<null> {
×
724
        return this.call<null>("message", { charId, ...options });
×
725
    }
×
726

×
727
    /**
×
728
     * Teleport to a node.
×
729
     * @param nodeId The ID of the node to teleport to.
×
730
     */
×
731
    async nodeTeleport(nodeId: string): Promise<null> {
×
732
        return this.teleport({ nodeId });
×
733
    }
×
734

×
735
    /**
×
736
     * Send an OOC message.
×
737
     * @param options The options for the OOC message.
×
738
     */
×
739
    async ooc(options: Commands.ControlledCharacter.OOCOptions): Promise<null> {
×
740
        return this.call<null>("ooc", options);
×
741
    }
×
742

×
743
    /** Send a ping to avoid being released for inactivity. */
×
744
    async ping(): Promise<null> {
×
745
        return this.call<null>("ping");
×
746
    }
×
747

×
748
    /**
×
749
     * Send a pose message.
×
750
     * @param msg The message to send.
×
751
     */
×
752
    async pose(msg: string): Promise<null> {
×
753
        return this.call<null>("pose", { msg });
×
754
    }
×
755

×
756
    /**
×
757
     * Mark a mail message as read. You must be using the username/password authentication method to use this.
×
758
     */
×
759
    async readMail(message: string): Promise<null> {
×
760
        return this.client.modules.core.getPlayer().then(player =>
×
761
            this.api.call<null>(ResourceIDs.PLAYER_MAIL_MESSAGE({ player: player.id, message }), "read")
×
762
        );
×
763
    }
×
764

×
765
    /**
×
766
     * Register a puppet character.
×
767
     * @param charId The ID of the character to register as a puppet.
×
768
     */
×
769
    async registerPuppet(charId: string): Promise<null> {
×
770
        return this.call<null>("registerPuppet", { charId });
×
771
    }
×
772

×
773
    /**
×
774
     * Reject control of a puppet.
×
775
     * @param charId The ID of the character to reject control of.
×
776
     * @param msg An optional message to include with the rejection.
×
777
     */
×
778
    async rejectControl(charId: string, msg?: string): Promise<null> {
×
779
        return this.call<null>("controlRequestReject", { charId, msg });
×
780
    }
×
781

×
782
    /**
×
783
     * Release control of this character.
×
784
     * @param msg A release message to show.
×
785
     */
×
786
    async release(msg?: string): Promise<null> {
×
787
        return this.call<null>("release", { msg });
×
788
    }
×
789

×
790
    /**
×
791
     * Remove a location.
×
792
     * @param locationId The ID of the location to remove.
×
793
     * @param type The type of the location.
×
794
     */
×
795
    async removeLocation(locationId: string, type: "area" | "room"): Promise<null> {
×
796
        return this.call<null>("removeLocation", { locationId, type });
×
797
    }
×
798

×
799
    /**
×
800
     * Remove a tag from this character.
×
801
     * @param tagId The ID of the tag to remove.
×
802
     */
×
803
    async removeTag(tagId: string): Promise<null> {
×
804
        return this.tags.remove(tagId);
×
805
    }
×
806

×
807
    /**
×
808
     * Remove a teleport node.
×
809
     * @param nodeId The ID of the node to remove.
×
810
     */
×
811
    async removeTeleport(nodeId: string): Promise<null> {
×
812
        return this.call<null>("removeTeleport", { nodeId });
×
813
    }
×
814

×
815
    /**
×
816
     * Request to create an exit.
×
817
     * @param name The name of the exit.
×
818
     * @param keys The keys to use for the exit.
×
819
     * @param targetRoom The ID of the room the exit leads to
×
820
     */
×
821
    async requestCreateExit(name: string, keys: Array<string>, targetRoom: string): Promise<Record<"exit" | "targetRoom", NameBasicResponse>> {
×
822
        return this.call<Record<"exit" | "targetRoom", NameBasicResponse>>("requestCreateExit", { name, keys, targetRoom });
×
823
    }
×
824

×
825
    /**
×
826
     * Request to set the owner of an area.
×
827
     * @param areaId The ID of the area to change ownership of.
×
828
     * @param charId The ID of the character to set as the new owner.
×
829
     * @returns The new owner character.
×
830
     */
×
831
    async requestSetAreaOwner(areaId: string, charId: string): Promise<Character> {
×
832
        return this.call<BasicCharacterResponse<"newOwner">>("requestSetAreaOwner", { areaId, charId })
×
833
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.newOwner.id })));
×
834
    }
×
835

×
836
    /**
×
837
     * Request to set the parent of an area.
×
838
     * @param areaId The ID of the area to change the parent of.
×
839
     * @param parentId The ID of the area to set as the new parent.
×
840
     */
×
841
    async requestSetAreaParent(areaId: string, parentId: string): Promise<null> {
×
842
        return this.call<null>("requestSetAreaParent", { areaId, parentId });
×
843
    }
×
844

×
845
    /**
×
846
     * Request to set a room.
×
847
     * @param roomId The ID of the room to set.
×
848
     * @param options The options to set.
×
849
     * @returns The updated room.
×
850
     */
×
851
    async requestSetRoom(roomId: string, options: Commands.ControlledCharacter.RequestSetRoomOptions): Promise<null> {
×
852
        return this.call<null>("requestSetRoom", { roomId, ...options });
×
853
    }
×
854

×
855
    /**
×
856
     * Request to set the area of a room.
×
857
     * @param roomId The ID of the room to set.
×
858
     * @param areaId The ID of the area to set.
×
859
     */
×
860
    async requestSetRoomArea(roomId: string, areaId: string): Promise<null> {
×
861
        return this.requestSetRoom(roomId, { areaId });
×
862
    }
×
863

×
864
    async requestSetRoomOwner(roomId: string, charId: string): Promise<Character> {
×
865
        return this.call<BasicCharacterResponse<"newOwner">>("requestSetRoomOwner", { roomId, charId })
×
866
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.newOwner.id })));
×
867
    }
×
868

×
869
    /**
×
870
     * Roll dice.
×
871
     * @param roll Accepts a number (sides), an array of `[amount, sides]` (e.g. `[2, 6]`), or a string consisting of a combination of dice sets `{amount}d{sides}` (e.g. `2d6`) and equations (e.g. `2d6+3`)
×
872
     * @param quiet If true, the roll will not be announced in the room.
×
873
     */
×
874
    async roll(roll: number | [amount: number, sides: number] | string, quiet = false): Promise<Messages.Roll> {
×
875
        const observer = new ResEventObserver<Messages.Roll>(this.client, this.rid, "out", { once: true, filter: (data): boolean => data.type === "roll" && data.char.id === this.id });
×
876
        if (typeof roll === "number") {
×
877
            roll = `1d${roll}`;
×
878
        } else if (Array.isArray(roll)) {
×
879
            roll = `${roll[0]}d${roll[1]}`;
×
880
        }
×
881
        const now = Date.now();
×
882
        return this.api.call<null>(ResourceIDs.ROLLER({ id: this.id }), "roll", { roll, quiet })
×
883
            .then(() => observer.get(500).catch(async() => {
×
884
                observer.end();
×
885
                const logs = await this.getLogEvents(now);
×
886
                return logs.events.find(l => l.type === "roll" && l.char.id === this.id) as Messages.Roll;
×
887
            }));
×
888
    }
×
889

×
890
    /**
×
891
     * Teleport to a room. You must own the room.
×
892
     * @param roomId The ID of the room to teleport to.
×
893
     */
×
894
    async roomTeleport(roomId: string): Promise<null> {
×
895
        return this.teleport({ roomId });
×
896
    }
×
897

×
898
    /**
×
899
     * Send a message.
×
900
     * @param msg The message to send.
×
901
     */
×
902
    async say(msg: string): Promise<null> {
×
903
        return this.call<null>("say", { msg });
×
904
    }
×
905

×
906
    /**
×
907
     * Set options for this character.
×
908
     * @param options The options to set.
×
909
     */
×
910
    async set(options: Commands.ControlledCharacter.SetOptions): Promise<null> {
×
911
        return this.call<null>("set", options);
×
912
    }
×
913

×
914
    /**
×
915
     * Set options for an area.
×
916
     * @param areaId The ID of the area to set.
×
917
     * @param options The options to set.
×
918
     * @returns The area.
×
919
     */
×
920
    async setArea(areaId: string, options: Commands.ControlledCharacter.SetAreaOptions): Promise<null> {
×
921
        // https://github.com/mucklet/mucklet-client/blob/8a0bc7c8e6b8e56c731ba0229116cfbfc1eae824/src/client/modules/main/commands/setArea/SetArea.js#L163-L164
×
922
        if (options.parentId === null) {
×
923
            delete options.parentId;
×
924
            await this.removeLocation(areaId, "area");
×
925
        }
×
926
        return this.call<null>("setArea", { areaId, ...options });
×
927
    }
×
928

×
929
    /**
×
930
     * Set the owner of an area.
×
931
     * @param areaId The ID of the area to.
×
932
     * @param charId The ID of the character to set as owner.
×
933
     * @returns The new owner character.
×
934
     */
×
935
    async setAreaOwner(areaId: string, charId: string): Promise<Character> {
×
936
        return this.call<BasicCharacterResponse<"newOwner">>("setAreaOwner", { areaId, charId })
×
937
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.newOwner.id })));
×
938
    }
×
939

×
940
    /**
×
941
     * Set options for an exit. You must own the room, and be present in it.
×
942
     * @param exitId The ID of the exit.
×
943
     * @param options The options to set.
×
944
     */
×
945
    async setExit(exitId: string, options: Commands.ControlledCharacter.SetExitOptions): Promise<null> {
×
946
        return this.call<null>("setExit", { exitId, ...options });
×
947
    }
×
948

×
949
    /**
×
950
     * Set the order of an exit.
×
951
     * @param exitId The ID of the exit.
×
952
     * @param order The new order of the exit.
×
953
     */
×
954
    async setExitOrder(exitId: string, order: number): Promise<null> {
×
955
        return this.call<null>("setExitOrder", { exitId, order });
×
956
    }
×
957

×
958
    /**
×
959
     * Set your home to your current room.
×
960
     */
×
961
    async setHome(): Promise<null> {
×
962
        return this.call<null>("setHome");
×
963
    }
×
964

×
965
    /**
×
966
     * Set location options.
×
967
     * @param locationId The ID of the location.
×
968
     * @param type The type of the location.
×
969
     * @param options The options to set.
×
970
     */
×
971
    async setLocation(locationId: string, type: "area" | "room", options: Commands.ControlledCharacter.SetLocation): Promise<null> {
×
972
        return this.call<null>("setLocation", { locationId, type, ...options });
×
973
    }
×
974

×
975
    /**
×
976
     * Set options for a profile.
×
977
     * @param profileId The ID of the profile to set.
×
978
     * @param options The options to set.
×
979
     * @returns The profile.
×
980
     */
×
981
    async setProfile(profileId: string, options: Commands.ControlledCharacter.SetProfileOptions): Promise<Profile> {
×
982
        return this.call<Record<"profile", NameBasicResponse>>("setProfile", { profileId, ...options })
×
983
            .then(r => this.api.get<Profile>(ResourceIDs.PROFILE({ id: r.profile.id })));
×
984
    }
×
985

×
986
    /**
×
987
     * Copy this character's image to the puppet.
×
988
     * @param puppetId The ID of the puppet to copy the image to.
×
989
     * @param options The options to set.
×
990
     * @returns The puppet.
×
991
     */
×
992
    async setPuppet(puppetId: string, options: Commands.ControlledCharacter.SetPuppetOptions): Promise<null> {
×
993
        // response unconfirmed
×
994
        return this.call<null>("setPuppet", { puppetId, ...options });
×
995
    }
×
996

×
997
    /**
×
998
     * Set options for a room.
×
999
     * @param roomId The ID of the room to set.
×
1000
     * @param options The options to set.
×
1001
     */
×
1002
    async setRoom(roomId: string, options: Commands.ControlledCharacter.SetRoomOptions): Promise<null> {
×
1003
        // https://github.com/mucklet/mucklet-client/blob/8a0bc7c8e6b8e56c731ba0229116cfbfc1eae824/src/client/modules/main/commands/setRoom/SetRoom.js#L183-L184
×
1004
        if (options.areaId === null) {
×
1005
            delete options.areaId;
×
1006
            await this.removeLocation(roomId, "room");
×
1007
        }
×
1008
        return this.call<null>("setRoom", { roomId, ...options });
×
1009
    }
×
1010

×
1011
    /**
×
1012
     * Set the owner of a room.
×
1013
     * @param roomId The ID of the room to set.
×
1014
     * @param charId The ID of the character to set as owner.
×
1015
     * @returns The new owner character.
×
1016
     */
×
1017
    async setRoomOwner(roomId: string, charId: string): Promise<Character> {
×
1018
        return this.call<BasicCharacterResponse<"newOwner">>("setRoomOwner", { roomId, charId })
×
1019
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.newOwner.id })));
×
1020
    }
×
1021

×
1022
    /**
×
1023
     * Set options for a room profile.
×
1024
     * @param profileId The ID of the room profile.
×
1025
     * @param options The options to set.
×
1026
     */
×
1027
    async setRoomProfile(profileId: string, options: Commands.ControlledCharacter.SetRoomProfileOptions): Promise<Record<"profile", NameBasicResponse>>  {
×
1028
        return this.call<Record<"profile", NameBasicResponse>>("setRoomProfile", { profileId, ...options });
×
1029
    }
×
1030

×
1031
    /**
×
1032
     * Set a room profile image.
×
1033
     * @param profileId The ID of the room profile.
×
1034
     * @param image The image to set. Either a fully qualified base64 url, or a buffer.
×
1035
     */
×
1036
    async setRoomProfileImage(profileId: string, image: string | Buffer): Promise<null> {
×
1037
        if (Buffer.isBuffer(image)) {
×
1038
            image = `data:${(await fileTypeFromBuffer(image))?.mime ?? "application/octet-stream"};base64,${image.toString("base64")}`;
×
1039
        }
×
1040
        return this.call<null>("setRoomProfileImage", { profileId, dataUrl: image });
×
1041
    }
×
1042

×
1043
    /**
×
1044
     * Set options for a room script.
×
1045
     * @param scriptId The ID of the script to set.
×
1046
     * @param options The options to set.
×
1047
     */
×
1048
    async setRoomScript(scriptId: string, options?: Commands.ControlledCharacter.SetRoomScriptOptions): Promise<{ room: Room; script: RoomScript; }> {
×
1049
        return this.call<{ room: NameBasicResponse; script: KeyBasicResponse; }>("setRoomScript", { scriptId, ...options }).then(async r => {
×
1050
            const [room, script] = await Promise.all([
×
1051
                this.api.get<Room>(ResourceIDs.ROOM({ id: r.room.id })),
×
1052
                this.api.get<RoomScript>(ResourceIDs.ROOMSCRIPT({ id: r.script.id }))
×
1053
            ]);
×
1054
            return { room, script };
×
1055
        });
×
1056
    }
×
1057

×
1058
    /**
×
1059
     * Set the status message this character.
×
1060
     * @param status The status message.
×
1061
     */
×
1062
    async setStatus(status: string): Promise<null> {
×
1063
        return this.call<null>("set", { status });
×
1064
    }
×
1065

×
1066
    /**
×
1067
     * Set a teleport node key.
×
1068
     * @param nodeId The ID of the node.
×
1069
     * @param options The options for the teleport node.
×
1070
     */
×
1071
    async setTeleport(nodeId: string, options: Commands.ControlledCharacter.SetTeleportOptions): Promise<null> {
×
1072
        return this.call<null>("setTeleport", { nodeId, ...options });
×
1073
    }
×
1074

×
1075
    /**
×
1076
     * Put this character to sleep.
×
1077
     * @param msg A sleep message to show.
×
1078
     * @note Alias of {@link release}
×
1079
     */
×
1080
    async sleep(msg?: string): Promise<null> {
×
1081
        return this.release(msg);
×
1082
    }
×
1083

×
1084
    /**
×
1085
     * Stop following a character.
×
1086
     */
×
1087
    async stopFollow(): Promise<null> {
×
1088
        return this.call<null>("stopFollow");
×
1089
    }
×
1090

×
1091
    /**
×
1092
     * Stop leading a character.
×
1093
     * @param charID The ID of the character to stop leading. If not provided, stops leading all characters.
×
1094
     */
×
1095
    async stopLead(charID?: string): Promise<null> {
×
1096
        return this.call<null>("stopLead", { charID });
×
1097
    }
×
1098

×
1099
    /** Stop LFRP status. */
×
1100
    async stopLfrp(): Promise<null> {
×
1101
        return this.call<null>("set", { rp: "" });
×
1102
    }
×
1103

×
1104
    /**
×
1105
     * Request to summon a character.
×
1106
     * @param charId The ID of the character to summon.
×
1107
     */
×
1108
    async summon(charId: string): Promise<null> {
×
1109
        return this.call<null>("summon", { charId });
×
1110
    }
×
1111

×
1112
    /**
×
1113
     * Sweep characters out of the room. If no character id is provided, sleeping characters are swept.
×
1114
     * @param charId The ID of a specific character to sweep. You must own the room.
×
1115
     */
×
1116
    async sweep(charId?: string): Promise<null> {
×
1117
        return this.call<null>("sweep", { charId });
×
1118
    }
×
1119

×
1120
    /**
×
1121
     * Sync a profile with this character's current details.
×
1122
     * @note Alias of {@link updateProfile}
×
1123
     * @param profileId The ID of the profile to sync.
×
1124
     */
×
1125
    async syncProfile(profileId: string): Promise<null> {
×
1126
        return this.updateProfile(profileId);
×
1127
    }
×
1128

×
1129
    /**
×
1130
     * Sync a room with its current details.
×
1131
     * @note Alias of {@link updateRoomProfile}
×
1132
     * @param profileId The ID of the room to sync.
×
1133
     */
×
1134
    async syncRoomProfile(profileId: string): Promise<null> {
×
1135
        return this.updateRoomProfile(profileId);
×
1136
    }
×
1137

×
1138
    /**
×
1139
     * Teleport.
×
1140
     * @param options The options for teleporting.
×
1141
     */
×
1142
    async teleport(options: Commands.ControlledCharacter.TeleportOptions): Promise<null> {
×
1143
        return this.call<null>("teleport", options);
×
1144
    }
×
1145

×
1146
    /**
×
1147
     * Teleport to your home.
×
1148
     */
×
1149
    async teleportHome(): Promise<null> {
×
1150
        return this.call<null>("teleportHome");
×
1151
    }
×
1152

×
1153
    /**
×
1154
     * Stop looking at a character.
×
1155
     */
×
1156
    async unlook(): Promise<null> {
×
1157
        return this.look(this.id);
×
1158
    }
×
1159

×
1160
    /**
×
1161
     * Sync a profile with your current details.
×
1162
     * @param profileId The ID of the profile to sync.
×
1163
     */
×
1164
    async updateProfile(profileId: string): Promise<null> {
×
1165
        return this.call<null>("updateProfile", { profileId });
×
1166
    }
×
1167

×
1168
    /**
×
1169
     * Sync a room with its current details.
×
1170
     * @param profileId The ID of the room to sync.
×
1171
     */
×
1172
    async updateRoomProfile(profileId: string): Promise<null> {
×
1173
        return this.call<null>("updateRoomProfile", { profileId });
×
1174
    }
×
1175

×
1176
    /**
×
1177
     * Use an exit.
×
1178
     * @param exitId The ID of the exit to use.
×
1179
     */
×
1180
    async useExit(exitId: string): Promise<null> {
×
1181
        return this.call<null>("useExit", { exitId });
×
1182
    }
×
1183

×
1184
    /**
×
1185
     * Apply a profile.
×
1186
     * @param profileId The ID of the profile to use.
×
1187
     * @param safe If a check should be made to ensure the character info is stored in a profile.
×
1188
     */
×
1189
    async useProfile(profileId: string, safe = true): Promise<Record<"profile", NameBasicResponse>> {
×
1190
        return this.call<Record<"profile", NameBasicResponse>>("useProfile", { profileId, safe });
×
1191
    }
×
1192

×
1193
    /**
×
1194
     * Apply a room profile. You must own the room and be in it.
×
1195
     * @param profileId The ID of the room profile to use.
×
1196
     * @param safe If a check should be made to ensure the current room info is stored in a profile.
×
1197
     */
×
1198
    async useRoomProfile(profileId: string, safe = true): Promise<Record<"profile", NameBasicResponse>> {
×
1199
        return this.call<Record<"profile", NameBasicResponse>>("useRoomProfile", { profileId, safe });
×
1200
    }
×
1201

×
1202
    /**
×
1203
     * Wake up this character.
×
1204
     * @param hidden If the character should be hidden from the awake list. Only applicable to bots.
×
1205
     * @param force Ignore the character already being awake.
×
1206
     */
×
1207
    async wakeup(hidden?: boolean, force = false): Promise<null> {
×
1208
        if (force && this.state === "awake") return null;
×
1209
        return this.call<null>("wakeup", { hidden });
×
1210
    }
×
1211

×
1212
    /**
×
1213
     * Send a whisper to a character. You must be in the same room as them.
×
1214
     * @param charId The ID of the character to whisper to.
×
1215
     * @param options The options for the whisper.
×
1216
     */
×
1217
    async whisper(charId: string, options: Commands.ControlledCharacter.WhisperOptions): Promise<null> {
×
1218
        return this.call<null>("whisper", { charId, ...options });
×
1219
    }
×
1220
}
×
1221

1✔
1222
export default ControlledCharacter;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc