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

WolferyScripting / WolferyJS / #11

28 Aug 2025 12:40PM UTC coverage: 55.392% (+0.02%) from 55.371%
#11

push

DonovanDMC
0.0.3

68 of 139 branches covered (48.92%)

Branch coverage included in aggregate %.

4149 of 7474 relevant lines covered (55.51%)

1.46 hits per line

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

3.35
/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 AfarRoom from "./AfarRoom.js";
1✔
14
import type Node from "./Node.js";
1✔
15
import type LookedAt from "./LookedAt.js";
1✔
16
import ResourceIDs from "../generated/ResourceIDs.js";
1✔
17
import {
1✔
18
    type KeyBasicResponse,
1✔
19
    type BasicCharacterResponse,
1✔
20
    type NameBasicResponse,
1✔
21
    type DeleteNameResponse,
1✔
22
    type Messages
1✔
23
} from "../util/types.js";
1✔
24
import type WolferyJS from "../WolferyJS.js";
1✔
25
import type Commands from "../util/commands.js";
1✔
26
import ResEventObserver from "../util/ResEventObserver.js";
1✔
27
import type { ControlledCharacterProperties } from "../generated/models/types.js";
1✔
28
import { ControlledCharacterDefinition } from "../generated/models/definitions.js";
1✔
29
import { PING_DURATION } from "../util/Constants.js";
1✔
30
import type RoomProfiles from "../collections/RoomProfiles.js";
1✔
31
import type RoomScripts from "../collections/RoomScripts.js";
1✔
32
import { fileTypeFromBuffer } from "file-type";
1✔
33
import type { AnyFunction, CollectionAddRemove, ResClient } from "resclient-ts";
1✔
34

1✔
35
declare interface ControlledCharacter extends BaseModel, ControlledCharacterProperties {}
1✔
36
// do not edit the first line of the class comment
1✔
37
/**
1✔
38
 * A controlled character.
1✔
39
 * @resourceID {@link ResourceIDs.CONTROLLED_CHARACTER | CONTROLLED_CHARACTER}
1✔
40
 * @resourceID {@link ResourceIDs.CONTROLLED_PUPPET | CONTROLLED_PUPPET}
1✔
41
 */
1✔
42
class ControlledCharacter extends BaseModel implements ControlledCharacterProperties {
×
43
    private _pingTimeout!: NodeJS.Timeout | null;
×
44
    private _roomProfiles!: RoomProfiles | null;
×
45
    private _roomScripts!: RoomScripts | null;
×
46
    private onChange = this._onChange.bind(this);
×
47
    private onExitAdd = this._onExitAdd.bind(this);
×
48
    private onExitChange = this._onExitChange.bind(this);
×
49
    private onExitRemove = this._onExitRemove.bind(this);
×
50
    private onLookedAtChange = this._onLookedAtChange.bind(this);
×
51
    private onNodeAdd = this._onNodeAdd.bind(this);
×
52
    private onNodeRemove = this._onNodeRemove.bind(this);
×
53
    private onOut = this._onOut.bind(this);
×
54
    private onOwnedAreaAdd = this._onOwnedAreaAdd.bind(this);
×
55
    private onOwnedAreaRemove = this._onOwnedAreaRemove.bind(this);
×
56
    private onOwnedRoomAdd = this._onOwnedRoomAdd.bind(this);
×
57
    private onOwnedRoomRemove = this._onOwnedRoomRemove.bind(this);
×
58
    private onProfileAdd = this._onProfileAdd.bind(this);
×
59
    private onProfileRemove = this._onProfileRemove.bind(this);
×
60
    private onRoomCharAdd = this._onRoomCharAdd.bind(this);
×
61
    private onRoomCharRemove = this._onRoomCharRemove.bind(this);
×
62
    private onRoomProfileAdd = this._onRoomProfileAdd.bind(this);
×
63
    private onRoomProfileRemove = this._onRoomProfileRemove.bind(this);
×
64
    private onRoomScriptAdd = this._onRoomScriptAdd.bind(this);
×
65
    private onRoomScriptRemove = this._onRoomScriptRemove.bind(this);
×
66
    private roomExitAwakeCharAddListeners!: Array<{ exit: Exit; fn: AnyFunction; }>;
×
67
    private roomExitAwakeCharRemoveListeners!: Array<{ exit: Exit; fn: AnyFunction; }>;
×
68
    constructor(client: WolferyJS, api: ResClient, rid: string) {
×
69
        super(client, api, rid, { definition: ControlledCharacterDefinition });
×
70
        this.p
×
71
            .writable("_pingTimeout", null)
×
72
            .writable("_roomProfiles", null)
×
73
            .writable("_roomScripts", null)
×
74
            .writable("onChange")
×
75
            .writable("onExitAdd")
×
76
            .writable("onExitChange")
×
77
            .writable("onExitRemove")
×
78
            .writable("onLookedAtChange")
×
79
            .writable("onNodeAdd")
×
80
            .writable("onNodeRemove")
×
81
            .writable("onOut")
×
82
            .writable("onOwnedAreaAdd")
×
83
            .writable("onOwnedAreaRemove")
×
84
            .writable("onOwnedRoomAdd")
×
85
            .writable("onOwnedRoomRemove")
×
86
            .writable("onProfileAdd")
×
87
            .writable("onProfileRemove")
×
88
            .writable("onRoomCharAdd")
×
89
            .writable("onRoomCharRemove")
×
90
            .writable("onRoomProfileAdd")
×
91
            .writable("onRoomProfileRemove")
×
92
            .writable("onRoomScriptAdd")
×
93
            .writable("onRoomScriptRemove")
×
94
            .writable("roomExitAwakeCharAddListeners", [])
×
95
            .writable("roomExitAwakeCharRemoveListeners", []);
×
96
    }
×
97

×
98
    private async _onChange(data: Partial<this>): Promise<void> {
×
99
        if (data.lookingAt !== undefined) {
×
100
            this.client.emit("lookAtChange", this, this.lookingAt?.char ?? null, data.lookingAt?.char ?? null);
×
101
        }
×
102

×
103
        if (data.lookedAt !== undefined) {
×
104
            await this._listenLookedAt(false, data.lookedAt);
×
105
            await this._listenLookedAt(true, this.lookedAt);
×
106
        }
×
107

×
108
        if (data.inRoom !== undefined) {
×
109
            await this._listenRoom(false, data.inRoom);
×
110
            await this._listenRoom(true, this.inRoom);
×
111
        }
×
112
    }
×
113

×
114
    private _onExitAdd(data: CollectionAddRemove<Exit>): void {
×
115
        this._listenExit(true, data.item);
×
116
        data.item.resourceOn("change", this.onExitChange);
×
117
        this.client.emit("exits.add", this, this.inRoom, data.item);
×
118
    }
×
119

×
120
    private _onExitChange(data: Partial<Exit>, exit: Exit): void {
×
121
        this.client.emit("exits.change", this, this.inRoom, exit, data);
×
122
        if (data.target !== undefined) {
×
123
            if (data.target === null) {
×
124
                this._listenExit(false, exit);
×
125
                this._listenExit(true, exit);
×
126
            } else {
×
127
                const add = this.roomExitAwakeCharAddListeners.find(e => e.exit.id === exit.id);
×
128
                const remove = this.roomExitAwakeCharRemoveListeners.find(e => e.exit.id === exit.id);
×
129
                if (add) {
×
130
                    data.target.awake?.resourceOff("add", add.fn);
×
131
                    this.roomExitAwakeCharAddListeners.splice(this.roomExitAwakeCharAddListeners.indexOf(add), 1);
×
132
                }
×
133
                if (remove) {
×
134
                    data.target.awake?.resourceOff("remove", remove.fn);
×
135
                    this.roomExitAwakeCharRemoveListeners.splice(this.roomExitAwakeCharRemoveListeners.indexOf(remove), 1);
×
136
                }
×
137
            }
×
138
        }
×
139
    }
×
140

×
141
    private _onExitRemove(data: CollectionAddRemove<Exit>): void {
×
142
        this._listenExit(false, data.item);
×
143
        data.item.resourceOff("change", this.onExitChange);
×
144
        this.client.emit("exits.remove", this, this.inRoom, data.item);
×
145
    }
×
146

×
147
    private async _onLookedAtChange(data: Record<string, true>): Promise<void> {
×
148
        const added: Array<string> = [], removed: Array<string> = [];
×
149
        for (const [key] of Object.entries(this.lookedAt.props)) {
×
150
            if (data[key] === undefined) {
×
151
                added.push(key);
×
152
            }
×
153
        }
×
154

×
155
        for (const [key] of Object.entries(data)) {
×
156
            if (this.lookedAt.props[key] === undefined) {
×
157
                removed.push(key);
×
158
            }
×
159
        }
×
160

×
161
        // we could mess around with RoomCharacter instances, but they can be unreliable, and we aren't missing anything
×
162
        for (const add of added) {
×
163
            const char = await this.api.get<Character>(ResourceIDs.CHARACTER({ id: add }));
×
164
            this.client.emit("lookedAt.add", this, char);
×
165
        }
×
166

×
167
        for (const remove of removed) {
×
168
            const char =  await this.api.get<Character>(ResourceIDs.CHARACTER({ id: remove }));
×
169
            this.client.emit("lookedAt.remove", this, char);
×
170
        }
×
171
    }
×
172

×
173
    private _onNodeAdd(data: CollectionAddRemove<Node>): void {
×
174
        this.client.emit("characterNodes.add", this, data.item);
×
175
    }
×
176

×
177
    private _onNodeRemove(data: CollectionAddRemove<Node>): void {
×
178
        this.client.emit("characterNodes.remove", this, data.item);
×
179
    }
×
180

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

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

×
186
        if (data.type === "privateDescribe") {
×
187
            const target = await this.api.get<Character>(ResourceIDs.CHARACTER({ id: data.target.id }));
×
188
            this.client.emit("message", "privateDescribe", sent, this, data.msg, target, data.script);
×
189
            return;
×
190
        }
×
191

×
192
        if (data.type === "info") {
×
193
            this.client.emit("message", "info", sent, this, data.msg);
×
194
            return;
×
195
        }
×
196

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

×
199
        switch (data.type) {
×
200
            case "wakeup":
×
201
            case "sleep":
×
202
            case "leave":
×
203
            case "arrive": {
×
204
                this.client.emit("message", data.type, sent, this, char, data.msg, data.method);
×
205
                break;
×
206
            }
×
207

×
208
            case "travel": {
×
209
                this.client.emit("message", "travel", sent, this, char, data.msg, data.targetRoom, data.method);
×
210
                break;
×
211
            }
×
212

×
213
            case "say":
×
214
            case "pose": {
×
215
                this.client.emit("message", data.type, sent, this, char, data.msg);
×
216
                break;
×
217
            }
×
218

×
219
            case "ooc": {
×
220
                this.client.emit("message", data.type, sent, this, char, data.msg, !!data.pose);
×
221
                break;
×
222
            }
×
223

×
224
            case "address":
×
225
            case "whisper":
×
226
            case "message":
×
227
            case "mail": {
×
228
                const target = await this.api.get<Character>(ResourceIDs.CHARACTER({ id: data.target.id }));
×
229
                this.client.emit("message", data.type, sent, this, char, data.msg, target, !!data.pose, !!data.ooc);
×
230
                break;
×
231
            }
×
232

×
233
            case "action":
×
234
            case "describe": {
×
235
                this.client.emit("message", data.type, sent, this, char, data.msg);
×
236
                break;
×
237
            }
×
238

×
239
            case "roll": {
×
240
                this.client.emit("message", "roll", sent, this, char, data.total, data.result, !!data.quiet);
×
241
                break;
×
242
            }
×
243
        }
×
244
    }
×
245

×
246
    private _onOwnedAreaAdd(data: CollectionAddRemove<Area>): void {
×
247
        this.client.emit("ownedAreas.add", this, data.item);
×
248
    }
×
249

×
250
    private _onOwnedAreaRemove(data: CollectionAddRemove<Area>): void {
×
251
        this.client.emit("ownedAreas.remove", this, data.item);
×
252
    }
×
253

×
254
    private _onOwnedRoomAdd(data: CollectionAddRemove<Room>): void {
×
255
        this.client.emit("ownedRooms.add", this, data.item);
×
256
    }
×
257

×
258
    private _onOwnedRoomRemove(data: CollectionAddRemove<Room>): void {
×
259
        this.client.emit("ownedRooms.remove", this, data.item);
×
260
    }
×
261

×
262
    private _onProfileAdd(data: CollectionAddRemove<Profile>): void {
×
263
        this.client.emit("profiles.add", this, data.item);
×
264
    }
×
265

×
266
    private _onProfileRemove(data: CollectionAddRemove<Profile>): void {
×
267
        this.client.emit("profiles.remove", this, data.item);
×
268
    }
×
269

×
270
    private _onRoomCharAdd(data: CollectionAddRemove<RoomCharacter>): void {
×
271
        this.client.emit("roomCharacters.add", this, this.inRoom, data.item);
×
272
    }
×
273

×
274
    private _onRoomCharRemove(data: CollectionAddRemove<RoomCharacter>): void {
×
275
        this.client.emit("roomCharacters.remove", this, this.inRoom, data.item);
×
276
    }
×
277

×
278
    private _onRoomExitAwakeCharAdd(char: Character, exit: Exit, room: AfarRoom): void {
×
279
        this.client.emit("roomCharacters.exit.add", this, room, exit, char);
×
280
    }
×
281

×
282
    private _onRoomExitAwakeCharRemove(char: Character, exit: Exit, room: AfarRoom): void {
×
283
        this.client.emit("roomCharacters.exit.remove", this, room, exit, char);
×
284
    }
×
285

×
286
    private _onRoomProfileAdd(data: CollectionAddRemove<RoomProfile>): void {
×
287
        this.client.emit("roomProfiles.add", this, this.inRoom, data.item);
×
288
    }
×
289

×
290
    private _onRoomProfileRemove(data: CollectionAddRemove<RoomProfile>): void {
×
291
        this.client.emit("roomProfiles.remove", this, this.inRoom, data.item);
×
292
    }
×
293

×
294
    private _onRoomScriptAdd(data: CollectionAddRemove<RoomScript>): void {
×
295
        this.client.emit("roomScripts.add", this, this.inRoom, data.item);
×
296
    }
×
297

×
298
    private _onRoomScriptRemove(data: CollectionAddRemove<RoomScript>): void {
×
299
        this.client.emit("roomScripts.remove", this, this.inRoom, data.item);
×
300
    }
×
301

×
302
    protected override async _listen(on: boolean): Promise<void> {
×
303
        await super._listen(on);
×
304
        const m = on ? "resourceOn" : "resourceOff";
×
305
        this[m]("change", this.onChange);
×
306
        this[m]("out", this.onOut);
×
307
        await this._listenLookedAt(on, this.lookedAt);
×
308

×
309
        if (this.client.options.pingCharacters) {
×
310
            if (on) {
×
311
                this._pingTimeout = setInterval(() => this.ping(),  PING_DURATION);
×
312
            } else if (this._pingTimeout) {
×
313
                clearInterval(this._pingTimeout);
×
314
            }
×
315
        }
×
316

×
317
        await this._listenRoom(on, this.inRoom);
×
318
        this.profiles[m]("add", this.onProfileAdd);
×
319
        this.profiles[m]("remove", this.onProfileRemove);
×
320
        this.ownedAreas[m]("add", this.onOwnedAreaAdd);
×
321
        this.ownedAreas[m]("remove", this.onOwnedAreaRemove);
×
322
        this.ownedRooms[m]("add", this.onOwnedRoomAdd);
×
323
        this.ownedRooms[m]("remove", this.onOwnedRoomRemove);
×
324
        this.nodes[m]("add", this.onNodeAdd);
×
325
    }
×
326

×
327
    protected _listenExit(on: boolean, exit: Exit): void {
×
328
        if (on && exit.target?.awake) {
×
329
            const room = exit.target;
×
330
            const add = (data: CollectionAddRemove<Character>): void => this._onRoomExitAwakeCharAdd(data.item, exit, room);
×
331
            const remove = (data: CollectionAddRemove<Character>): void => this._onRoomExitAwakeCharRemove(data.item, exit, room);
×
332
            exit.target.awake.resourceOn("add", add);
×
333
            exit.target.awake.resourceOn("remove", remove);
×
334
            this.roomExitAwakeCharAddListeners.push({ exit, fn: add });
×
335
            this.roomExitAwakeCharRemoveListeners.push({ exit, fn: remove });
×
336
        } else if  (!on) {
×
337
            const add = this.roomExitAwakeCharAddListeners.find(e => e.exit.id === exit.id);
×
338
            const remove = this.roomExitAwakeCharRemoveListeners.find(e => e.exit.id === exit.id);
×
339
            if (add) {
×
340
                exit.target?.awake?.resourceOff("add", add.fn);
×
341
                this.roomExitAwakeCharAddListeners.splice(this.roomExitAwakeCharAddListeners.indexOf(add), 1);
×
342
            }
×
343
            if (remove) {
×
344
                exit.target?.awake?.resourceOff("remove", remove.fn);
×
345
                this.roomExitAwakeCharRemoveListeners.splice(this.roomExitAwakeCharRemoveListeners.indexOf(remove), 1);
×
346
            }
×
347
        }
×
348
    }
×
349

×
350
    protected async _listenLookedAt(on: boolean, lookedAt: LookedAt): Promise<void> {
×
351
        const m = on ? "resourceOn" : "resourceOff";
×
352
        lookedAt[m]("change", this.onLookedAtChange);
×
353
    }
×
354

×
355
    protected async _listenRoom(on: boolean, room: RoomDetails): Promise<void> {
×
356
        if (on) {
×
357
            room.exits.resourceOn("add", this.onExitAdd);
×
358
            room.exits.resourceOn("remove", this.onExitRemove);
×
359
            for (const exit of room.exits) {
×
360
                exit.resourceOn("change", this.onExitChange);
×
361
                this._listenExit(on, exit);
×
362
            }
×
363
            room.chars?.resourceOn("add", this.onRoomCharAdd);
×
364
            room.chars?.resourceOn("remove", this.onRoomCharRemove);
×
365
            if (room.owner.id === this.id) {
×
366
                // we own the room, listen for profile & script changes
×
367
                this._roomProfiles = await room.getProfiles();
×
368
                this._roomScripts = await room.getScripts();
×
369
                this._roomProfiles.resourceOn("add", this.onRoomProfileAdd);
×
370
                this._roomProfiles.resourceOn("remove", this.onRoomProfileRemove);
×
371
                this._roomScripts.resourceOn("add", this.onRoomScriptAdd);
×
372
                this._roomScripts.resourceOn("remove", this.onRoomScriptRemove);
×
373
            }
×
374
        } else {
×
375
            room.exits.resourceOff("add", this.onExitAdd);
×
376
            room.exits.resourceOff("remove", this.onExitRemove);
×
377
            for (const exit of room.exits) {
×
378
                exit.resourceOff("change", this.onExitChange);
×
379
                this._listenExit(on, exit);
×
380
            }
×
381
            this.roomExitAwakeCharAddListeners = [];
×
382
            this.roomExitAwakeCharRemoveListeners = [];
×
383
            room.chars?.resourceOff("add", this.onRoomCharAdd);
×
384
            room.chars?.resourceOff("remove", this.onRoomCharRemove);
×
385
            if (this._roomProfiles) {
×
386
                this._roomProfiles.resourceOff("add", this.onRoomProfileAdd);
×
387
                this._roomProfiles.resourceOff("remove", this.onRoomProfileRemove);
×
388
                this._roomProfiles = null;
×
389
            }
×
390
            if (this._roomScripts) {
×
391
                this._roomScripts.resourceOff("add", this.onRoomScriptAdd);
×
392
                this._roomScripts.resourceOff("remove", this.onRoomScriptRemove);
×
393
                this._roomScripts = null;
×
394
            }
×
395
        }
×
396
    }
×
397

×
398
    get avatarURL(): string | null {
×
399
        return this.avatar === "" ? null : `${this.client.fileURL}/core/char/avatar/${this.avatar}`;
×
400
    }
×
401

×
402
    get fullname(): string {
×
403
        return `${this.name} ${this.surname}`.trim();
×
404
    }
×
405

×
406
    get isPuppet(): boolean {
×
407
        return ResourceIDs.CONTROLLED_PUPPET.regex.test(this.rid);
×
408
    }
×
409

×
410
    /**
×
411
     * Accept a control request for a character.
×
412
     * @param charId The ID of the character to accept control for.
×
413
     */
×
414
    async acceptControl(charId: string): Promise<null> {
×
415
        return this.call<null>("controlRequestAccept", { charId });
×
416
    }
×
417

×
418
    /**
×
419
     * Add a tag to this character.
×
420
     * @param tagId The ID of the tag to add.
×
421
     * @param pref The preference for the tag.
×
422
     */
×
423
    async addTag(tagId: string, pref: "like" | "dislike"): Promise<null> {
×
424
        return this.tags.add(tagId, pref);
×
425
    }
×
426

×
427
    /**
×
428
     * Add a new teleport node.
×
429
     * @param options The options for the teleport node.
×
430
     */
×
431
    async addTeleport(options: Commands.ControlledCharacter.AddTeleportOptions): Promise<null> {
×
432
        return this.call<null>("addTeleport", options);
×
433
    }
×
434

×
435
    /**
×
436
     * Address a character. This is the equivalent of the `@` command in the web client.
×
437
     * @param charId The ID of the character to address.
×
438
     * @param options The options for addressing the character.
×
439
     */
×
440
    async address(charId: string, options: Commands.ControlledCharacter.AddressOptions): Promise<null> {
×
441
        return this.call<null>("address", { charId, ...options });
×
442
    }
×
443

×
444
    /** Set this character as away (afk). */
×
445
    async away(status?: string): Promise<null> {
×
446
        return this.call<null>("away", { status });
×
447
    }
×
448

×
449
    /**
×
450
     * Copy this character's avatar to the profile.
×
451
     * @param profileId The ID of the profile to copy the avatar to.
×
452
     * @returns The profile.
×
453
     */
×
454
    async copyProfileAvatar(profileId: string): Promise<Profile> {
×
455
        return this.call<Record<"profile", NameBasicResponse>>("copyProfileAvatar", { profileId })
×
456
            .then(r => this.api.get<Profile>(ResourceIDs.PROFILE({ id: r.profile.id })));
×
457
    }
×
458

×
459
    /**
×
460
     * Copy this character's image to the profile.
×
461
     * @param profileId The ID of the profile to copy the image to.
×
462
     * @returns The profile.
×
463
     */
×
464
    async copyProfileImage(profileId: string): Promise<Profile> {
×
465
        return this.call<Record<"profile", NameBasicResponse>>("copyProfileImage", { profileId })
×
466
            .then(r => this.api.get<Profile>(ResourceIDs.PROFILE({ id: r.profile.id })));
×
467
    }
×
468

×
469
    /**
×
470
     * Create a new area.
×
471
     * @param name The name of the area.
×
472
     */
×
473
    async createArea(name: string): Promise<Area> {
×
474
        return this.call<NameBasicResponse>("createArea", { name })
×
475
            .then(r => this.client.waitForCached<Area>(ResourceIDs.AREA({ id: r.id })));
×
476
    }
×
477

×
478
    /**
×
479
     * Create an exit to another room.
×
480
     * @param name The name of the exit.
×
481
     * @param keys The keys to use to go through the exit.
×
482
     * @param targetRoom The ID of the room to go to. Provide `null` to create a new room.
×
483
     */
×
484
    async createExit(name: string, keys: Array<string>, targetRoom: string | null): Promise<{ exit: Exit; targetRoom: Room; }> {
×
485
        return this.call<Record<"exit" | "targetRoom", NameBasicResponse>>("createExit", { name, keys, targetRoom }).then(async r => {
×
486
            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 }))]);
×
487
            return { exit, targetRoom: room };
×
488
        });
×
489
    }
×
490

×
491
    /**
×
492
     * Create a profile based on this character's current attributes.
×
493
     * @param name The name of the profile.
×
494
     * @param key The key of the profile.
×
495
     */
×
496
    async createProfile(name: string, key: string): Promise<Profile> {
×
497
        return this.call<Record<"profile", NameBasicResponse>>("createProfile", { name, key }).then(r => this.client.waitForCached<Profile>(ResourceIDs.PROFILE({ id: r.profile.id })));
×
498
    }
×
499

×
500
    /**
×
501
     * Create a room.
×
502
     * @param name The name of the room.
×
503
     */
×
504
    async createRoom(name: string): Promise<Room> {
×
505
        return this.call<NameBasicResponse>("createRoom", { name }).then(r => this.client.waitForCached<Room>(ResourceIDs.ROOM({ id: r.id })));
×
506
    }
×
507

×
508
    /**
×
509
     * Create a room profile. You must own the room, and be present in it.
×
510
     * @param name The name of the room profile.
×
511
     * @param key The key of the room profile.
×
512
     */
×
513
    async createRoomProfile(name: string, key: string): Promise<RoomProfile> {
×
514
        return this.call<Record<"profile", NameBasicResponse>>("createRoomProfile", { name, key }).then(r => this.client.waitForCached<RoomProfile>(ResourceIDs.ROOM_PROFILE({ id: r.profile.id })));
×
515
    }
×
516

×
517
    /**
×
518
     * Create a room script. It will be created in the room the character is located in.
×
519
     * @param key The key of the room script.
×
520
     * @param options The options for the room script.
×
521
     */
×
522
    async createRoomScript(key: string, options?: Commands.ControlledCharacter.CreateRoomScriptOptions): Promise<{ room: Room; script: RoomScript; }> {
×
523
        return this.call<{ room: NameBasicResponse; script: KeyBasicResponse; }>("createRoomScript", { key, ...options }).then(async r => {
×
524
            const [room, script] = await Promise.all([
×
525
                this.api.get<Room>(ResourceIDs.ROOM({ id: r.room.id })),
×
526
                this.api.get<RoomScript>(ResourceIDs.ROOMSCRIPT({ id: r.script.id }))
×
527
            ]);
×
528
            return { room, script };
×
529
        });
×
530
    }
×
531

×
532
    /**
×
533
     * Delete an area.
×
534
     * @param areaId The ID of the area to delete.
×
535
     */
×
536
    async deleteArea(areaId: string): Promise<{ area: Area; response: DeleteNameResponse; }> {
×
537
        const area = await this.api.get<Area>(ResourceIDs.AREA({ id: areaId }));
×
538
        return this.call<DeleteNameResponse>("deleteArea", { areaId })
×
539
            .then(r => ({ area, response: r }));
×
540
    }
×
541

×
542
    /**
×
543
     * Delete an exit.
×
544
     * @param exitId The ID of the exit to delete.
×
545
     */
×
546
    async deleteExit(exitId: string): Promise<Exit> {
×
547
        const exit = this.api.get<Exit>(ResourceIDs.EXIT({ id: exitId }));
×
548
        return this.call<null>("deleteExit", { exitId }).then(() => exit);
×
549
    }
×
550

×
551
    /**
×
552
     * Delete a mail message. You must be using the username/password authentication method to use this.
×
553
     * @param messageId The ID of the message to delete.
×
554
     */
×
555
    async deleteMail(messageId: string): Promise<null> {
×
556
        return this.client.modules.core.getPlayer().then(player =>
×
557
            this.api.call<null>(ResourceIDs.PLAYER_MAIL_MESSAGE({ player: player.id, message: messageId }), "delete")
×
558
        );
×
559
    }
×
560

×
561
    /**
×
562
     * Delete a profile.
×
563
     * @param profileId The ID of the profile to delete.
×
564
     */
×
565
    async deleteProfile(profileId: string): Promise<Profile> {
×
566
        const profile = await this.api.get<Profile>(ResourceIDs.PROFILE({ id: profileId }));
×
567
        return this.call<Record<"profile", NameBasicResponse>>("deleteProfile", { profileId })
×
568
            .then(() => profile);
×
569
    }
×
570

×
571
    /**
×
572
     * Delete a room.
×
573
     * @param roomId The ID of the room to delete.
×
574
     */
×
575
    async deleteRoom(roomId: string): Promise<Room> {
×
576
        const room = await this.api.get<Room>(ResourceIDs.ROOM({ id: roomId }));
×
577
        return this.call<null>("deleteRoom", { roomId })
×
578
            .then(() => room);
×
579
    }
×
580

×
581
    /**
×
582
     * Delete a room profile. You must own the room, and be present in it.
×
583
     * @param profileId The ID of the room profile to delete.
×
584
     */
×
585
    async deleteRoomProfile(profileId: string): Promise<Record<"profile", NameBasicResponse>> {
×
586
        return this.call<Record<"profile", NameBasicResponse>>("deleteRoomProfile", { profileId });
×
587
    }
×
588

×
589
    /**
×
590
     * Delete a room script. You must own the room, and be present in it.
×
591
     * @param scriptId The ID of the room script to delete.
×
592
     */
×
593
    async deleteRoomScript(scriptId: string): Promise<{ room: Room; script: RoomScript | KeyBasicResponse; }> {
×
594
        return this.call<{ room: NameBasicResponse; script: KeyBasicResponse; }>("deleteRoomScript", { scriptId }).then(async r => {
×
595
            const room = await this.api.get<Room>(ResourceIDs.ROOM({ id: r.room.id }));
×
596
            const script = this.api.getCached<RoomScript>(ResourceIDs.ROOMSCRIPT({ id: r.script.id })) ?? r.script;
×
597
            return { room, script };
×
598
        });
×
599
    }
×
600

×
601
    /**
×
602
     * Describe a scene or action.
×
603
     * @param msg The message.
×
604
     */
×
605
    async describe(msg: string): Promise<null> {
×
606
        return this.call<null>("describe", { msg });
×
607
    }
×
608

×
609
    /**
×
610
     * Evict a character from their teleport nodes or home for the current room.
×
611
     * @param charId The ID of the character to evict.
×
612
     * @returns The evicted character.
×
613
     */
×
614
    async evict(charId: string): Promise<Character> {
×
615
        return this.call<BasicCharacterResponse<"targetChar">>("evict", { charId })
×
616
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.targetChar.id })));
×
617
    }
×
618

×
619
    /**
×
620
     * Evict a character from their home in the current room.
×
621
     * @param charId The ID of the character to evict.
×
622
     * @returns The evicted character.
×
623
     */
×
624
    async evictHome(charId: string): Promise<Character> {
×
625
        return this.call<BasicCharacterResponse<"char">>("evictHome", { charId })
×
626
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.char.id })));
×
627
    }
×
628

×
629
    /**
×
630
     * Evict a character from controlling a puppet.
×
631
     * @param charId The ID of the character to evict.
×
632
     * @param puppetId The ID of the puppet to evict.
×
633
     * @returns The evicted character and puppet.
×
634
     */
×
635
    async evictPuppeteer(charId: string, puppetId: string): Promise<{ char: Character; puppet: Character; }> {
×
636
        return this.call<BasicCharacterResponse<"char"> & BasicCharacterResponse<"puppet">>("evictPuppeteer", { charId, puppetId })
×
637
            .then(async r => {
×
638
                const [char, puppet] = await Promise.all([
×
639
                    this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.char.id })),
×
640
                    this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.puppet.id }))
×
641
                ]);
×
642
                return { char, puppet };
×
643
            });
×
644
    }
×
645

×
646
    /**
×
647
     * Evict a character from their teleport node.
×
648
     * @param charId The ID of the character to evict.
×
649
     * @returns The evicted character.
×
650
     */
×
651
    async evictTeleport(charId: string): Promise<Character> {
×
652
        return this.call<BasicCharacterResponse<"char">>("evictTeleport", { charId })
×
653
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.char.id })));
×
654
    }
×
655

×
656
    /**
×
657
     * Focus a character.
×
658
     * Focus a character.
×
659
     * @param targetId The ID of the character to focus.
×
660
     * @param options The options for focusing the character.
×
661
     * @returns The focused character.
×
662
     */
×
663
    async focusChar(targetId: string, options: Omit<Commands.Player.FocusCharOptions, "targetId">): Promise<Character> {
×
664
        return this.client.modules.core.getPlayer().then(player =>
×
665
            player.focusChar(this.id, { targetId, ...options })
×
666
        );
×
667
    }
×
668

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

×
677
    /**
×
678
     * Get the character for this controlled character.
×
679
     * @returns The character.
×
680
     */
×
681
    async getChar(): Promise<Character> {
×
682
        return this.api.get<Character>(ResourceIDs.CHARACTER({ id: this.id }));
×
683
    }
×
684

×
685
    /**
×
686
     * Get an exit in the current room.
×
687
     * @param options The options for getting the exit.
×
688
     * @returns The exit.
×
689
     */
×
690
    async getExit(options: Commands.ControlledCharacter.GetExitOptions): Promise<Exit> {
×
691
        return this.call<Exit>("getExit", options);
×
692
    }
×
693

×
694
    async getLogEvents(startTime?: number): Promise<Commands.LogEvents> {
×
695
        return this.api.call<Commands.LogEvents>("log.events", "get", { charId: this.id, startTime });
×
696
    }
×
697

×
698
    /**
×
699
     * Get the owned character for this controlled character. If the character is a puppet, use {@link getPuppet} instead.
×
700
     * @returns The owned character.
×
701
     */
×
702
    async getOwnedChar(): Promise<OwnedCharacter> {
×
703
        return this.api.get<OwnedCharacter>(ResourceIDs.OWNED_CHARACTER({ id: this.id }));
×
704
    }
×
705

×
706
    /**
×
707
     * Get the puppet for this controlled character. If the character is not a puppet, use {@link getOwnedChar} instead.
×
708
     * @returns The controlled puppet.
×
709
     */
×
710
    async getPuppet(): Promise<Puppet> {
×
711
        const { char, puppet } = ResourceIDs.CONTROLLED_PUPPET.parts(this.rid);
×
712
        return this.api.get<Puppet>(ResourceIDs.PUPPET({ char, puppet }));
×
713
    }
×
714

×
715
    /**
×
716
     * Get the room character for this controlled character.
×
717
     * @returns The room character.
×
718
     */
×
719
    async getRoomChar(): Promise<RoomCharacter> {
×
720
        return this.api.get<RoomCharacter>(ResourceIDs.ROOM_CHARACTER({ id: this.id }));
×
721
    }
×
722

×
723
    /**
×
724
     * Send a message to the helpers.
×
725
     * @param msg The message.
×
726
     */
×
727
    async helpme(msg: string): Promise<null> {
×
728
        return this.call<null>("helpme", { msg });
×
729
    }
×
730

×
731
    /**
×
732
     * Request to join a character.
×
733
     * @param charId The ID of the character to join.
×
734
     */
×
735
    async join(charId: string): Promise<null> {
×
736
        return this.call<null>("join", { charId });
×
737
    }
×
738

×
739
    /**
×
740
     * Request to lead a character.
×
741
     * @param charId The ID of the character to lead.
×
742
     */
×
743
    async lead(charId: string): Promise<null> {
×
744
        return this.call<null>("lead", { charId });
×
745
    }
×
746

×
747
    /**
×
748
     * Set the LFRP status for the character.
×
749
     * @param msg The message to display when LFRP is enabled.
×
750
     */
×
751
    async lfrp(msg?: string): Promise<null> {
×
752
        if (msg) {
×
753
            await this.client.modules.core.getPlayer().then(player => player.setCharSettings(this.id, { lfrpDesc: msg }));
×
754
        }
×
755

×
756
        return this.call<null>("set", { rp: "lfrp" });
×
757
    }
×
758

×
759

×
760
    /**
×
761
     * Look at a character.
×
762
     * @param charId The ID of the character to look at.
×
763
     */
×
764
    async look(charId: string): Promise<null> {
×
765
        return this.call<null>("look", { charId });
×
766
    }
×
767

×
768
    /**
×
769
     * Send a mail to a character. You must be using the username/password authentication method to use this.
×
770
     * @param toCharId The ID of the character to send the mail to.
×
771
     * @param options The options for the mail.
×
772
     */
×
773
    async mail(toCharId: string, options: Commands.Inbox.SendOptions): Promise<Character> {
×
774
        return this.client.modules.core.getPlayer().then(player => player.getInbox()).then(inbox => inbox.send(this.id, toCharId, options));
×
775
    }
×
776

×
777
    /**
×
778
     * Send a message to a character.
×
779
     * @param charId The ID of the character to send the message to.
×
780
     * @param options The options for sending the message.
×
781
     */
×
782
    async message(charId: string, options: Commands.ControlledCharacter.MessageOptions): Promise<null> {
×
783
        return this.call<null>("message", { charId, ...options });
×
784
    }
×
785

×
786
    /**
×
787
     * Teleport to a node.
×
788
     * @param nodeId The ID of the node to teleport to.
×
789
     */
×
790
    async nodeTeleport(nodeId: string): Promise<null> {
×
791
        return this.teleport({ nodeId });
×
792
    }
×
793

×
794
    /**
×
795
     * Send an OOC message.
×
796
     * @param options The options for the OOC message.
×
797
     */
×
798
    async ooc(options: Commands.ControlledCharacter.OOCOptions): Promise<null> {
×
799
        return this.call<null>("ooc", options);
×
800
    }
×
801

×
802
    /** Send a ping to avoid being released for inactivity. */
×
803
    async ping(): Promise<null> {
×
804
        return this.call<null>("ping");
×
805
    }
×
806

×
807
    /**
×
808
     * Send a pose message.
×
809
     * @param msg The message to send.
×
810
     */
×
811
    async pose(msg: string): Promise<null> {
×
812
        return this.call<null>("pose", { msg });
×
813
    }
×
814

×
815
    /**
×
816
     * Mark a mail message as read. You must be using the username/password authentication method to use this.
×
817
     */
×
818
    async readMail(message: string): Promise<null> {
×
819
        return this.client.modules.core.getPlayer().then(player =>
×
820
            this.api.call<null>(ResourceIDs.PLAYER_MAIL_MESSAGE({ player: player.id, message }), "read")
×
821
        );
×
822
    }
×
823

×
824
    /**
×
825
     * Register a puppet character.
×
826
     * @param charId The ID of the character to register as a puppet.
×
827
     */
×
828
    async registerPuppet(charId: string): Promise<null> {
×
829
        return this.call<null>("registerPuppet", { charId });
×
830
    }
×
831

×
832
    /**
×
833
     * Reject control of a puppet.
×
834
     * @param charId The ID of the character to reject control of.
×
835
     * @param msg An optional message to include with the rejection.
×
836
     */
×
837
    async rejectControl(charId: string, msg?: string): Promise<null> {
×
838
        return this.call<null>("controlRequestReject", { charId, msg });
×
839
    }
×
840

×
841
    /**
×
842
     * Release control of this character.
×
843
     * @param msg A release message to show.
×
844
     */
×
845
    async release(msg?: string): Promise<null> {
×
846
        return this.call<null>("release", { msg });
×
847
    }
×
848

×
849
    /**
×
850
     * Remove a location.
×
851
     * @param locationId The ID of the location to remove.
×
852
     * @param type The type of the location.
×
853
     */
×
854
    async removeLocation(locationId: string, type: "area" | "room"): Promise<null> {
×
855
        return this.call<null>("removeLocation", { locationId, type });
×
856
    }
×
857

×
858
    /**
×
859
     * Remove a tag from this character.
×
860
     * @param tagId The ID of the tag to remove.
×
861
     */
×
862
    async removeTag(tagId: string): Promise<null> {
×
863
        return this.tags.remove(tagId);
×
864
    }
×
865

×
866
    /**
×
867
     * Remove a teleport node.
×
868
     * @param nodeId The ID of the node to remove.
×
869
     */
×
870
    async removeTeleport(nodeId: string): Promise<null> {
×
871
        return this.call<null>("removeTeleport", { nodeId });
×
872
    }
×
873

×
874
    /**
×
875
     * Request to create an exit.
×
876
     * @param name The name of the exit.
×
877
     * @param keys The keys to use for the exit.
×
878
     * @param targetRoom The ID of the room the exit leads to
×
879
     */
×
880
    async requestCreateExit(name: string, keys: Array<string>, targetRoom: string): Promise<Record<"exit" | "targetRoom", NameBasicResponse>> {
×
881
        return this.call<Record<"exit" | "targetRoom", NameBasicResponse>>("requestCreateExit", { name, keys, targetRoom });
×
882
    }
×
883

×
884
    /**
×
885
     * Request to set the owner of an area.
×
886
     * @param areaId The ID of the area to change ownership of.
×
887
     * @param charId The ID of the character to set as the new owner.
×
888
     * @returns The new owner character.
×
889
     */
×
890
    async requestSetAreaOwner(areaId: string, charId: string): Promise<Character> {
×
891
        return this.call<BasicCharacterResponse<"newOwner">>("requestSetAreaOwner", { areaId, charId })
×
892
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.newOwner.id })));
×
893
    }
×
894

×
895
    /**
×
896
     * Request to set the parent of an area.
×
897
     * @param areaId The ID of the area to change the parent of.
×
898
     * @param parentId The ID of the area to set as the new parent.
×
899
     */
×
900
    async requestSetAreaParent(areaId: string, parentId: string): Promise<null> {
×
901
        return this.call<null>("requestSetAreaParent", { areaId, parentId });
×
902
    }
×
903

×
904
    /**
×
905
     * Request to set a room.
×
906
     * @param roomId The ID of the room to set.
×
907
     * @param options The options to set.
×
908
     * @returns The updated room.
×
909
     */
×
910
    async requestSetRoom(roomId: string, options: Commands.ControlledCharacter.RequestSetRoomOptions): Promise<null> {
×
911
        return this.call<null>("requestSetRoom", { roomId, ...options });
×
912
    }
×
913

×
914
    /**
×
915
     * Request to set the area of a room.
×
916
     * @param roomId The ID of the room to set.
×
917
     * @param areaId The ID of the area to set.
×
918
     */
×
919
    async requestSetRoomArea(roomId: string, areaId: string): Promise<null> {
×
920
        return this.requestSetRoom(roomId, { areaId });
×
921
    }
×
922

×
923
    async requestSetRoomOwner(roomId: string, charId: string): Promise<Character> {
×
924
        return this.call<BasicCharacterResponse<"newOwner">>("requestSetRoomOwner", { roomId, charId })
×
925
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.newOwner.id })));
×
926
    }
×
927

×
928
    /**
×
929
     * Roll dice.
×
930
     * @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`)
×
931
     * @param quiet If true, the roll will not be announced in the room.
×
932
     */
×
933
    async roll(roll: number | [amount: number, sides: number] | string, quiet = false): Promise<Messages.Roll> {
×
934
        const observer = new ResEventObserver<Messages.Roll>(this.client, this.rid, "out", { once: true, filter: (data): boolean => data.type === "roll" && data.char.id === this.id });
×
935
        if (typeof roll === "number") {
×
936
            roll = `1d${roll}`;
×
937
        } else if (Array.isArray(roll)) {
×
938
            roll = `${roll[0]}d${roll[1]}`;
×
939
        }
×
940
        const now = Date.now();
×
941
        return this.api.call<null>(ResourceIDs.ROLLER({ id: this.id }), "roll", { roll, quiet })
×
942
            .then(() => observer.get(500).catch(async() => {
×
943
                observer.end();
×
944
                const logs = await this.getLogEvents(now);
×
945
                return logs.events.find(l => l.type === "roll" && l.char.id === this.id) as Messages.Roll;
×
946
            }));
×
947
    }
×
948

×
949
    /**
×
950
     * Teleport to a room. You must own the room.
×
951
     * @param roomId The ID of the room to teleport to.
×
952
     */
×
953
    async roomTeleport(roomId: string): Promise<null> {
×
954
        return this.teleport({ roomId });
×
955
    }
×
956

×
957
    /**
×
958
     * Send a message.
×
959
     * @param msg The message to send.
×
960
     */
×
961
    async say(msg: string): Promise<null> {
×
962
        return this.call<null>("say", { msg });
×
963
    }
×
964

×
965
    /**
×
966
     * Set options for this character.
×
967
     * @param options The options to set.
×
968
     */
×
969
    async set(options: Commands.ControlledCharacter.SetOptions): Promise<null> {
×
970
        return this.call<null>("set", options);
×
971
    }
×
972

×
973
    /**
×
974
     * Set options for an area.
×
975
     * @param areaId The ID of the area to set.
×
976
     * @param options The options to set.
×
977
     * @returns The area.
×
978
     */
×
979
    async setArea(areaId: string, options: Commands.ControlledCharacter.SetAreaOptions): Promise<null> {
×
980
        // https://github.com/mucklet/mucklet-client/blob/8a0bc7c8e6b8e56c731ba0229116cfbfc1eae824/src/client/modules/main/commands/setArea/SetArea.js#L163-L164
×
981
        if (options.parentId === null) {
×
982
            delete options.parentId;
×
983
            await this.removeLocation(areaId, "area");
×
984
        }
×
985
        return this.call<null>("setArea", { areaId, ...options });
×
986
    }
×
987

×
988
    /**
×
989
     * Set the owner of an area.
×
990
     * @param areaId The ID of the area to.
×
991
     * @param charId The ID of the character to set as owner.
×
992
     * @returns The new owner character.
×
993
     */
×
994
    async setAreaOwner(areaId: string, charId: string): Promise<Character> {
×
995
        return this.call<BasicCharacterResponse<"newOwner">>("setAreaOwner", { areaId, charId })
×
996
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.newOwner.id })));
×
997
    }
×
998

×
999
    /**
×
1000
     * Set options for an exit. You must own the room, and be present in it.
×
1001
     * @param exitId The ID of the exit.
×
1002
     * @param options The options to set.
×
1003
     */
×
1004
    async setExit(exitId: string, options: Commands.ControlledCharacter.SetExitOptions): Promise<null> {
×
1005
        return this.call<null>("setExit", { exitId, ...options });
×
1006
    }
×
1007

×
1008
    /**
×
1009
     * Set the order of an exit.
×
1010
     * @param exitId The ID of the exit.
×
1011
     * @param order The new order of the exit.
×
1012
     */
×
1013
    async setExitOrder(exitId: string, order: number): Promise<null> {
×
1014
        return this.call<null>("setExitOrder", { exitId, order });
×
1015
    }
×
1016

×
1017
    /**
×
1018
     * Set your home to your current room.
×
1019
     */
×
1020
    async setHome(): Promise<null> {
×
1021
        return this.call<null>("setHome");
×
1022
    }
×
1023

×
1024
    /**
×
1025
     * Set location options.
×
1026
     * @param locationId The ID of the location.
×
1027
     * @param type The type of the location.
×
1028
     * @param options The options to set.
×
1029
     */
×
1030
    async setLocation(locationId: string, type: "area" | "room", options: Commands.ControlledCharacter.SetLocation): Promise<null> {
×
1031
        return this.call<null>("setLocation", { locationId, type, ...options });
×
1032
    }
×
1033

×
1034
    /**
×
1035
     * Set options for a profile.
×
1036
     * @param profileId The ID of the profile to set.
×
1037
     * @param options The options to set.
×
1038
     * @returns The profile.
×
1039
     */
×
1040
    async setProfile(profileId: string, options: Commands.ControlledCharacter.SetProfileOptions): Promise<Profile> {
×
1041
        return this.call<Record<"profile", NameBasicResponse>>("setProfile", { profileId, ...options })
×
1042
            .then(r => this.api.get<Profile>(ResourceIDs.PROFILE({ id: r.profile.id })));
×
1043
    }
×
1044

×
1045
    /**
×
1046
     * Copy this character's image to the puppet.
×
1047
     * @param puppetId The ID of the puppet to copy the image to.
×
1048
     * @param options The options to set.
×
1049
     * @returns The puppet.
×
1050
     */
×
1051
    async setPuppet(puppetId: string, options: Commands.ControlledCharacter.SetPuppetOptions): Promise<null> {
×
1052
        // response unconfirmed
×
1053
        return this.call<null>("setPuppet", { puppetId, ...options });
×
1054
    }
×
1055

×
1056
    /**
×
1057
     * Set options for a room.
×
1058
     * @param roomId The ID of the room to set.
×
1059
     * @param options The options to set.
×
1060
     */
×
1061
    async setRoom(roomId: string, options: Commands.ControlledCharacter.SetRoomOptions): Promise<null> {
×
1062
        // https://github.com/mucklet/mucklet-client/blob/8a0bc7c8e6b8e56c731ba0229116cfbfc1eae824/src/client/modules/main/commands/setRoom/SetRoom.js#L183-L184
×
1063
        if (options.areaId === null) {
×
1064
            delete options.areaId;
×
1065
            await this.removeLocation(roomId, "room");
×
1066
        }
×
1067
        return this.call<null>("setRoom", { roomId, ...options });
×
1068
    }
×
1069

×
1070
    /**
×
1071
     * Set the owner of a room.
×
1072
     * @param roomId The ID of the room to set.
×
1073
     * @param charId The ID of the character to set as owner.
×
1074
     * @returns The new owner character.
×
1075
     */
×
1076
    async setRoomOwner(roomId: string, charId: string): Promise<Character> {
×
1077
        return this.call<BasicCharacterResponse<"newOwner">>("setRoomOwner", { roomId, charId })
×
1078
            .then(r => this.api.get<Character>(ResourceIDs.CHARACTER({ id: r.newOwner.id })));
×
1079
    }
×
1080

×
1081
    /**
×
1082
     * Set options for a room profile.
×
1083
     * @param profileId The ID of the room profile.
×
1084
     * @param options The options to set.
×
1085
     */
×
1086
    async setRoomProfile(profileId: string, options: Commands.ControlledCharacter.SetRoomProfileOptions): Promise<Record<"profile", NameBasicResponse>>  {
×
1087
        return this.call<Record<"profile", NameBasicResponse>>("setRoomProfile", { profileId, ...options });
×
1088
    }
×
1089

×
1090
    /**
×
1091
     * Set a room profile image.
×
1092
     * @param profileId The ID of the room profile.
×
1093
     * @param image The image to set. Either a fully qualified base64 url, or a buffer.
×
1094
     */
×
1095
    async setRoomProfileImage(profileId: string, image: string | Buffer): Promise<null> {
×
1096
        if (Buffer.isBuffer(image)) {
×
1097
            image = `data:${(await fileTypeFromBuffer(image))?.mime ?? "application/octet-stream"};base64,${image.toString("base64")}`;
×
1098
        }
×
1099
        return this.call<null>("setRoomProfileImage", { profileId, dataUrl: image });
×
1100
    }
×
1101

×
1102
    /**
×
1103
     * Set options for a room script.
×
1104
     * @param scriptId The ID of the script to set.
×
1105
     * @param options The options to set.
×
1106
     */
×
1107
    async setRoomScript(scriptId: string, options?: Commands.ControlledCharacter.SetRoomScriptOptions): Promise<{ room: Room; script: RoomScript; }> {
×
1108
        return this.call<{ room: NameBasicResponse; script: KeyBasicResponse; }>("setRoomScript", { scriptId, ...options }).then(async r => {
×
1109
            const [room, script] = await Promise.all([
×
1110
                this.api.get<Room>(ResourceIDs.ROOM({ id: r.room.id })),
×
1111
                this.api.get<RoomScript>(ResourceIDs.ROOMSCRIPT({ id: r.script.id }))
×
1112
            ]);
×
1113
            return { room, script };
×
1114
        });
×
1115
    }
×
1116

×
1117
    /**
×
1118
     * Set the status message this character.
×
1119
     * @param status The status message.
×
1120
     */
×
1121
    async setStatus(status: string): Promise<null> {
×
1122
        return this.call<null>("set", { status });
×
1123
    }
×
1124

×
1125
    /**
×
1126
     * Set a teleport node key.
×
1127
     * @param nodeId The ID of the node.
×
1128
     * @param options The options for the teleport node.
×
1129
     */
×
1130
    async setTeleport(nodeId: string, options: Commands.ControlledCharacter.SetTeleportOptions): Promise<null> {
×
1131
        return this.call<null>("setTeleport", { nodeId, ...options });
×
1132
    }
×
1133

×
1134
    /**
×
1135
     * Put this character to sleep.
×
1136
     * @param msg A sleep message to show.
×
1137
     * @note Alias of {@link release}
×
1138
     */
×
1139
    async sleep(msg?: string): Promise<null> {
×
1140
        return this.release(msg);
×
1141
    }
×
1142

×
1143
    /**
×
1144
     * Stop following a character.
×
1145
     */
×
1146
    async stopFollow(): Promise<null> {
×
1147
        return this.call<null>("stopFollow");
×
1148
    }
×
1149

×
1150
    /**
×
1151
     * Stop leading a character.
×
1152
     * @param charID The ID of the character to stop leading. If not provided, stops leading all characters.
×
1153
     */
×
1154
    async stopLead(charID?: string): Promise<null> {
×
1155
        return this.call<null>("stopLead", { charID });
×
1156
    }
×
1157

×
1158
    /** Stop LFRP status. */
×
1159
    async stopLfrp(): Promise<null> {
×
1160
        return this.call<null>("set", { rp: "" });
×
1161
    }
×
1162

×
1163
    /**
×
1164
     * Request to summon a character.
×
1165
     * @param charId The ID of the character to summon.
×
1166
     */
×
1167
    async summon(charId: string): Promise<null> {
×
1168
        return this.call<null>("summon", { charId });
×
1169
    }
×
1170

×
1171
    /**
×
1172
     * Sweep characters out of the room. If no character id is provided, sleeping characters are swept.
×
1173
     * @param charId The ID of a specific character to sweep. You must own the room.
×
1174
     */
×
1175
    async sweep(charId?: string): Promise<null> {
×
1176
        return this.call<null>("sweep", { charId });
×
1177
    }
×
1178

×
1179
    /**
×
1180
     * Sync a profile with this character's current details.
×
1181
     * @note Alias of {@link updateProfile}
×
1182
     * @param profileId The ID of the profile to sync.
×
1183
     */
×
1184
    async syncProfile(profileId: string): Promise<null> {
×
1185
        return this.updateProfile(profileId);
×
1186
    }
×
1187

×
1188
    /**
×
1189
     * Sync a room with its current details.
×
1190
     * @note Alias of {@link updateRoomProfile}
×
1191
     * @param profileId The ID of the room to sync.
×
1192
     */
×
1193
    async syncRoomProfile(profileId: string): Promise<null> {
×
1194
        return this.updateRoomProfile(profileId);
×
1195
    }
×
1196

×
1197
    /**
×
1198
     * Teleport.
×
1199
     * @param options The options for teleporting.
×
1200
     */
×
1201
    async teleport(options: Commands.ControlledCharacter.TeleportOptions): Promise<null> {
×
1202
        return this.call<null>("teleport", options);
×
1203
    }
×
1204

×
1205
    /**
×
1206
     * Teleport to your home.
×
1207
     */
×
1208
    async teleportHome(): Promise<null> {
×
1209
        return this.call<null>("teleportHome");
×
1210
    }
×
1211

×
1212
    /**
×
1213
     * Stop looking at a character.
×
1214
     */
×
1215
    async unlook(): Promise<null> {
×
1216
        return this.look(this.id);
×
1217
    }
×
1218

×
1219
    /**
×
1220
     * Sync a profile with your current details.
×
1221
     * @param profileId The ID of the profile to sync.
×
1222
     */
×
1223
    async updateProfile(profileId: string): Promise<null> {
×
1224
        return this.call<null>("updateProfile", { profileId });
×
1225
    }
×
1226

×
1227
    /**
×
1228
     * Sync a room with its current details.
×
1229
     * @param profileId The ID of the room to sync.
×
1230
     */
×
1231
    async updateRoomProfile(profileId: string): Promise<null> {
×
1232
        return this.call<null>("updateRoomProfile", { profileId });
×
1233
    }
×
1234

×
1235
    /**
×
1236
     * Use an exit.
×
1237
     * @param exitId The ID of the exit to use.
×
1238
     */
×
1239
    async useExit(exitId: string): Promise<null> {
×
1240
        return this.call<null>("useExit", { exitId });
×
1241
    }
×
1242

×
1243
    /**
×
1244
     * Apply a profile.
×
1245
     * @param profileId The ID of the profile to use.
×
1246
     * @param safe If a check should be made to ensure the character info is stored in a profile.
×
1247
     */
×
1248
    async useProfile(profileId: string, safe = true): Promise<Record<"profile", NameBasicResponse>> {
×
1249
        return this.call<Record<"profile", NameBasicResponse>>("useProfile", { profileId, safe });
×
1250
    }
×
1251

×
1252
    /**
×
1253
     * Apply a room profile. You must own the room and be in it.
×
1254
     * @param profileId The ID of the room profile to use.
×
1255
     * @param safe If a check should be made to ensure the current room info is stored in a profile.
×
1256
     */
×
1257
    async useRoomProfile(profileId: string, safe = true): Promise<Record<"profile", NameBasicResponse>> {
×
1258
        return this.call<Record<"profile", NameBasicResponse>>("useRoomProfile", { profileId, safe });
×
1259
    }
×
1260

×
1261
    /**
×
1262
     * Wake up this character.
×
1263
     * @param hidden If the character should be hidden from the awake list. Only applicable to bots.
×
1264
     * @param force Ignore the character already being awake.
×
1265
     */
×
1266
    async wakeup(hidden?: boolean, force = false): Promise<null> {
×
1267
        if (force && this.state === "awake") return null;
×
1268
        return this.call<null>("wakeup", { hidden });
×
1269
    }
×
1270

×
1271
    /**
×
1272
     * Send a whisper to a character. You must be in the same room as them.
×
1273
     * @param charId The ID of the character to whisper to.
×
1274
     * @param options The options for the whisper.
×
1275
     */
×
1276
    async whisper(charId: string, options: Commands.ControlledCharacter.WhisperOptions): Promise<null> {
×
1277
        return this.call<null>("whisper", { charId, ...options });
×
1278
    }
×
1279
}
×
1280

1✔
1281
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