@vespura/vmenu-plugin 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js ADDED
@@ -0,0 +1,267 @@
1
+ // src/version.ts
2
+ var version = true ? "0.0.1" : "0.0.0-local";
3
+
4
+ // src/format.ts
5
+ function formatFloat(value) {
6
+ const text = value.toFixed(4).replace(/0+$/, "");
7
+ return text.endsWith(".") ? `${text}0` : text;
8
+ }
9
+
10
+ // src/protocol.ts
11
+ var SERVER_PROTOCOL_VERSION = 1;
12
+ var PluginEvents = {
13
+ Probe: "vMenu.Enhanced:Plugins:Probe",
14
+ Register: "vMenu.Enhanced:Plugins:Register",
15
+ Unregister: "vMenu.Enhanced:Plugins:Unregister",
16
+ Update: "vMenu.Enhanced:Plugins:Update",
17
+ Notify: "vMenu.Enhanced:Plugins:Notify",
18
+ Prompt: "vMenu.Enhanced:Plugins:Prompt",
19
+ SetTheme: "vMenu.Enhanced:Plugins:SetTheme",
20
+ RegisterThemes: "vMenu.Enhanced:Plugins:RegisterThemes",
21
+ Ready: "vMenu.Enhanced:Plugins:Ready",
22
+ ServerProbe: "vMenu.Enhanced:Plugins:Server:Probe",
23
+ ServerRegister: "vMenu.Enhanced:Plugins:Server:Register",
24
+ ServerDenied: "vMenu.Enhanced:Plugins:Server:Denied",
25
+ ServerReady: "vMenu.Enhanced:Plugins:Server:Ready",
26
+ readyFor: (resource) => `vMenu.Enhanced:Plugins:${resource}:Ready`,
27
+ registerResultFor: (resource) => `vMenu.Enhanced:Plugins:${resource}:RegisterResult`,
28
+ eventFor: (resource) => `vMenu.Enhanced:Plugins:${resource}:Event`,
29
+ promptResultFor: (resource) => `vMenu.Enhanced:Plugins:${resource}:PromptResult`,
30
+ themesFor: (resource) => `vMenu.Enhanced:Plugins:${resource}:Themes`,
31
+ themesRegisteredFor: (resource) => `vMenu.Enhanced:Plugins:${resource}:ThemesRegistered`,
32
+ serverReadyFor: (resource) => `vMenu.Enhanced:Plugins:${resource}:Server:Ready`,
33
+ serverRegisterResultFor: (resource) => `vMenu.Enhanced:Plugins:${resource}:Server:RegisterResult`
34
+ };
35
+ var RefreshPermissionsEvent = "vMenu.Enhanced:Permissions:Refresh";
36
+ var SettingTypes = {
37
+ Bool: "bool",
38
+ Int: "int",
39
+ Float: "float",
40
+ String: "string"
41
+ };
42
+ function normalizeResult(raw) {
43
+ if (!raw || typeof raw !== "object") {
44
+ return void 0;
45
+ }
46
+ return {
47
+ accepted: raw.accepted === true,
48
+ protocolVersion: typeof raw.protocolVersion === "number" ? raw.protocolVersion : 0,
49
+ errors: Array.isArray(raw.errors) ? raw.errors : [],
50
+ warnings: Array.isArray(raw.warnings) ? raw.warnings : []
51
+ };
52
+ }
53
+
54
+ // src/server/declaration.ts
55
+ var ServerPluginDeclaration = class {
56
+ /** @param displayName Used in the generated example files, so owners see which plugin a section belongs to. */
57
+ constructor(displayName) {
58
+ this.displayName = displayName;
59
+ this.permissions = [];
60
+ this.settings = [];
61
+ this.loggedItems = [];
62
+ }
63
+ /** Declares a permission. `staffOnly` marks it as staff only in the generated permissions example. */
64
+ addPermission(name, description, staffOnly = false) {
65
+ this.permissions.push({ name, description, staffOnly });
66
+ return this;
67
+ }
68
+ /**
69
+ * Lets the server owner see a line in their webhook whenever somebody uses this row. The client
70
+ * half still has to set `log` on it. `description` is a noun phrase dropped into vMenu's own
71
+ * wording, as in "turned the anti-grief shield on".
72
+ */
73
+ addLoggedItem(itemId, description) {
74
+ this.loggedItems.push({ itemId, description });
75
+ return this;
76
+ }
77
+ /** Declares an on or off setting. */
78
+ addBoolSetting(name, defaultValue, description) {
79
+ return this.add(name, SettingTypes.Bool, defaultValue ? "true" : "false", description);
80
+ }
81
+ /** Declares a whole number setting. */
82
+ addIntSetting(name, defaultValue, description) {
83
+ return this.add(name, SettingTypes.Int, String(Math.trunc(defaultValue)), description);
84
+ }
85
+ /** Declares a decimal number setting. */
86
+ addFloatSetting(name, defaultValue, description) {
87
+ return this.add(name, SettingTypes.Float, formatFloat(defaultValue), description);
88
+ }
89
+ /** Declares a text setting. */
90
+ addStringSetting(name, defaultValue, description) {
91
+ return this.add(name, SettingTypes.String, defaultValue, description);
92
+ }
93
+ /** @internal */
94
+ toRequest() {
95
+ return {
96
+ protocolVersion: SERVER_PROTOCOL_VERSION,
97
+ displayName: this.displayName,
98
+ permissions: [...this.permissions],
99
+ settings: [...this.settings],
100
+ loggedItems: [...this.loggedItems]
101
+ };
102
+ }
103
+ add(name, type, defaultText, description) {
104
+ this.settings.push({ name, type, default: defaultText, description });
105
+ return this;
106
+ }
107
+ };
108
+
109
+ // src/fivem.ts
110
+ var natives = globalThis;
111
+ var currentResource = () => natives.GetCurrentResourceName();
112
+ var emitLocal = (eventName, ...args) => natives.emit(eventName, ...args);
113
+ var onLocal = (eventName, handler) => natives.on(eventName, handler);
114
+ var isAceAllowed = (playerSource, object) => natives.IsPlayerAceAllowed(playerSource, object) === true;
115
+ var log = {
116
+ info: (resource, message) => console.log(`[${resource}] ${message}`),
117
+ warn: (resource, message) => console.warn(`[${resource}] ${message}`),
118
+ error: (resource, message) => console.error(`[${resource}] ${message}`)
119
+ };
120
+ function parseJson(json) {
121
+ if (typeof json !== "string") {
122
+ return void 0;
123
+ }
124
+ try {
125
+ return JSON.parse(json);
126
+ } catch {
127
+ return void 0;
128
+ }
129
+ }
130
+ function sanitizeId(resource) {
131
+ return resource.replace(/[^A-Za-z0-9]/g, "_");
132
+ }
133
+
134
+ // src/signal.ts
135
+ var Signal = class {
136
+ constructor(resource, name) {
137
+ this.resource = resource;
138
+ this.name = name;
139
+ this.handlers = [];
140
+ }
141
+ get hasHandlers() {
142
+ return this.handlers.length > 0;
143
+ }
144
+ add(handler) {
145
+ this.handlers.push(handler);
146
+ return () => {
147
+ this.handlers = this.handlers.filter((existing) => existing !== handler);
148
+ };
149
+ }
150
+ fire(...args) {
151
+ for (const handler of [...this.handlers]) {
152
+ try {
153
+ handler(...args);
154
+ } catch (error) {
155
+ log.error(this.resource(), `A ${this.name} handler threw: ${describe(error)}`);
156
+ }
157
+ }
158
+ }
159
+ };
160
+ function describe(error) {
161
+ return error instanceof Error ? error.stack ?? error.message : String(error);
162
+ }
163
+
164
+ // src/server/server.ts
165
+ var PermissionPrefix = "vMenu.Enhanced.Plugins";
166
+ var Everything = "vMenu.Enhanced.Everything";
167
+ var declaration;
168
+ var firstResult;
169
+ var resolveFirst;
170
+ var handlersRegistered = false;
171
+ var resourceName = "";
172
+ var pluginId = "";
173
+ var registrationAnswered = new Signal(() => resourceName, "registration answered");
174
+ function ensureIdentity() {
175
+ if (resourceName.length === 0) {
176
+ resourceName = currentResource();
177
+ pluginId = sanitizeId(resourceName);
178
+ }
179
+ }
180
+ function sendRegistration() {
181
+ if (declaration) {
182
+ emitLocal(PluginEvents.ServerRegister, JSON.stringify(declaration.toRequest()));
183
+ }
184
+ }
185
+ function onResult(json) {
186
+ const result = normalizeResult(parseJson(json));
187
+ if (!result) {
188
+ log.warn(resourceName, "vMenu sent a registration answer that did not parse.");
189
+ return;
190
+ }
191
+ for (const error of result.errors) {
192
+ log.error(resourceName, `vMenu refused the plugin registration: ${error}`);
193
+ }
194
+ for (const warning of result.warnings) {
195
+ log.warn(resourceName, `vMenu accepted the plugin registration with a note: ${warning}`);
196
+ }
197
+ resolveFirst?.(result);
198
+ resolveFirst = void 0;
199
+ registrationAnswered.fire(result);
200
+ }
201
+ function ensureHandlers() {
202
+ if (handlersRegistered) {
203
+ return;
204
+ }
205
+ handlersRegistered = true;
206
+ ensureIdentity();
207
+ onLocal(PluginEvents.ServerReady, () => sendRegistration());
208
+ onLocal(PluginEvents.serverReadyFor(resourceName), () => sendRegistration());
209
+ onLocal(PluginEvents.serverRegisterResultFor(resourceName), (json) => onResult(json));
210
+ }
211
+ var VMenuServer = {
212
+ /**
213
+ * Declares the plugin with vMenu. The promise resolves on vMenu's first answer, which can be a
214
+ * while when vMenu starts later than the plugin. It never rejects: a refusal arrives as a result
215
+ * with `accepted` false.
216
+ */
217
+ register(pluginDeclaration) {
218
+ declaration = pluginDeclaration;
219
+ firstResult ?? (firstResult = new Promise((resolve) => {
220
+ resolveFirst = resolve;
221
+ }));
222
+ ensureHandlers();
223
+ emitLocal(PluginEvents.ServerProbe);
224
+ return firstResult;
225
+ },
226
+ /**
227
+ * Whether a player holds one of the plugin's own permissions, by its short name. Also honours the
228
+ * container grants a server owner may have used instead of the exact name.
229
+ */
230
+ isPlayerAllowed(playerSource, permissionName) {
231
+ const source = String(playerSource ?? "");
232
+ if (source.length === 0) {
233
+ return false;
234
+ }
235
+ ensureIdentity();
236
+ const scope = `${PermissionPrefix}.${pluginId}`;
237
+ return isAceAllowed(source, `${scope}.${permissionName}`) || isAceAllowed(source, `${scope}.All`) || isAceAllowed(source, `${PermissionPrefix}.All`) || isAceAllowed(source, Everything);
238
+ },
239
+ /**
240
+ * The same check as `isPlayerAllowed`, but a refusal is also reported to vMenu's security webhook.
241
+ * Use it where a legitimate client could only ever have sent the thing you are handling while
242
+ * allowed, and keep `isPlayerAllowed` for ordinary branching.
243
+ */
244
+ requirePermission(playerSource, permissionName) {
245
+ if (VMenuServer.isPlayerAllowed(playerSource, permissionName)) {
246
+ return true;
247
+ }
248
+ const source = String(playerSource ?? "");
249
+ if (source.length > 0) {
250
+ emitLocal(PluginEvents.ServerDenied, source, permissionName);
251
+ }
252
+ return false;
253
+ },
254
+ /** Refreshes permissions for one or more players. Passing no ids refreshes every connected player. */
255
+ refreshPermissions(...serverIds) {
256
+ emitLocal(RefreshPermissionsEvent, serverIds.map((id) => Math.trunc(Number(id))));
257
+ },
258
+ /** Called on every registration answer, including automatic re-registrations. */
259
+ onRegistrationAnswered(handler) {
260
+ return registrationAnswered.add(handler);
261
+ }
262
+ };
263
+ export {
264
+ ServerPluginDeclaration,
265
+ VMenuServer,
266
+ version
267
+ };
@@ -0,0 +1,4 @@
1
+ import type { GateNode, TextRef } from '../protocol';
2
+ export declare function sameText(left: TextRef | undefined, right: TextRef | undefined): boolean;
3
+ export declare function sameTexts(left: TextRef[] | undefined, right: TextRef[] | undefined): boolean;
4
+ export declare function sameGate(left: GateNode | undefined, right: GateNode | undefined): boolean;
@@ -0,0 +1,24 @@
1
+ import type { PluginBoolSetting } from './settings';
2
+ /** A gate, or a permission's short name as a shortcut for `Gate.permission(name)`. */
3
+ export type GateLike = Gate | string;
4
+ /**
5
+ * Decides whether a menu item is available to the player, evaluated live by vMenu. Combine gates
6
+ * with `and`, `or`, `Gate.all` and `Gate.any`. Names are short: vMenu scopes them to your plugin, so
7
+ * you can never gate on another plugin's permissions or settings.
8
+ */
9
+ export declare class Gate {
10
+ private readonly node;
11
+ private constructor();
12
+ /** One of the permissions your server side declared, by its short name. */
13
+ static permission(shortName: string): Gate;
14
+ /** One of your bool settings: the item is available while the convar reads true. */
15
+ static setting(setting: PluginBoolSetting | string): Gate;
16
+ /** Passes when every gate passes. */
17
+ static all(...gates: GateLike[]): Gate;
18
+ /** Passes when at least one gate passes. */
19
+ static any(...gates: GateLike[]): Gate;
20
+ /** Passes when this gate and the other one both pass. */
21
+ and(other: GateLike): Gate;
22
+ /** Passes when this gate or the other one passes. */
23
+ or(other: GateLike): Gate;
24
+ }
@@ -0,0 +1,18 @@
1
+ import type { UpdateOp } from '../protocol';
2
+ import type { PluginItem } from './item';
3
+ import type { PluginKey } from './key';
4
+ import type { PluginMenu } from './menu';
5
+ export interface PluginHost {
6
+ readonly resource: string;
7
+ readonly isConnected: boolean;
8
+ nextItemId(): string;
9
+ nextMenuId(): string;
10
+ registerMenu(menu: PluginMenu): void;
11
+ registerItem(item: PluginItem): void;
12
+ registerKey(key: PluginKey): void;
13
+ unregisterItem(item: PluginItem): void;
14
+ emitOp(op: UpdateOp): void;
15
+ emitAdd(menu: PluginMenu, item: PluginItem, add: UpdateOp): void;
16
+ filterChanged(menu: PluginMenu): void;
17
+ mergeTranslations(code: string, entries: Record<string, string>): void;
18
+ }
@@ -0,0 +1,14 @@
1
+ export { version } from '../version';
2
+ export { Text, type TextLike } from '../text';
3
+ export type { Unsubscribe } from '../signal';
4
+ export type { RegisterResult } from '../protocol';
5
+ export { Gate, type GateLike } from './gate';
6
+ export { PluginSetting, PluginBoolSetting, PluginIntSetting, PluginFloatSetting, PluginStringSetting, PluginSettings, } from './settings';
7
+ export { PluginItem, type ItemOptions } from './item';
8
+ export { PluginButton, PluginConfirmButton, PluginCheckbox, PluginList, PluginConfirmList, PluginSlider, PluginDynamicList, PluginSeparator, PluginSubmenu, } from './items';
9
+ export { PluginKey, type PluginKeyPress } from './key';
10
+ export { PluginMenu, type CheckboxOptions, type ConfirmOptions, type KeyOptions, type ListOptions, type SliderOptions, type SubmenuOptions, } from './menu';
11
+ export { PluginPlayerActions, PluginPlayerButton, PluginPlayerConfirmButton, PluginPlayerList, type PlayerTarget, } from './playerActions';
12
+ export { PluginThemes, type PluginTheme } from './themes';
13
+ export { PluginTranslations } from './translations';
14
+ export { VMenuPlugin, type NotifyStyle, type PluginPrompt, type PromptSuggestion } from './plugin';
@@ -0,0 +1,71 @@
1
+ import { type UpdateOp } from '../protocol';
2
+ import { Signal, type Unsubscribe } from '../signal';
3
+ import { type TextLike } from '../text';
4
+ import { type GateLike } from './gate';
5
+ /** Properties every row accepts when you add it. Setting them here sends them along with the row itself. */
6
+ export interface ItemOptions {
7
+ /** A stable id of your own. Generated when left out. Unique within your plugin. */
8
+ id?: string;
9
+ description?: TextLike;
10
+ label?: TextLike;
11
+ lockedDescription?: TextLike;
12
+ gate?: GateLike;
13
+ hideWhenLocked?: boolean;
14
+ visible?: boolean;
15
+ enabled?: boolean;
16
+ log?: boolean;
17
+ leftIcon?: string;
18
+ rightIcon?: string;
19
+ }
20
+ /**
21
+ * One row in your plugin's menus. Setting a property updates the row live once the plugin is
22
+ * connected, and everything set before connecting rides along with the registration.
23
+ */
24
+ export declare abstract class PluginItem {
25
+ private textValue?;
26
+ private descriptionValue?;
27
+ private labelValue?;
28
+ private lockedDescriptionValue?;
29
+ private gateValue?;
30
+ private readonly highlighted;
31
+ /** The row's id, how vMenu refers to it. */
32
+ get id(): string;
33
+ /** The row's text. */
34
+ get text(): TextLike | undefined;
35
+ set text(value: TextLike | undefined);
36
+ /** The line shown under the menu while the row is highlighted. */
37
+ get description(): TextLike | undefined;
38
+ set description(value: TextLike | undefined);
39
+ /** Right aligned text. Ignored by rows whose label the menu draws itself. */
40
+ get label(): TextLike | undefined;
41
+ set label(value: TextLike | undefined);
42
+ /** What the row says while its gate locks it. Empty uses vMenu's own wording. */
43
+ get lockedDescription(): TextLike | undefined;
44
+ set lockedDescription(value: TextLike | undefined);
45
+ /** Decides whether the row is available, evaluated live by vMenu. */
46
+ get gate(): GateLike | undefined;
47
+ set gate(value: GateLike | undefined);
48
+ /** What a failing gate does to the row: greyed out with a lock, or gone entirely. */
49
+ get hideWhenLocked(): boolean;
50
+ set hideWhenLocked(value: boolean);
51
+ /** Whether the row is shown at all. */
52
+ get visible(): boolean;
53
+ set visible(value: boolean);
54
+ /** A disabled row is greyed out but still visible. Independent of the gate. */
55
+ get enabled(): boolean;
56
+ set enabled(value: boolean);
57
+ /**
58
+ * Ask vMenu to log use of this row to the server owner's webhook. Does nothing on its own: the
59
+ * plugin's server half has to declare the same id with `addLoggedItem`.
60
+ */
61
+ get log(): boolean;
62
+ set log(value: boolean);
63
+ /** Icon names from the vMenu icon set, for example "LOCK" or "STAR". */
64
+ setIcons(leftIcon?: string, rightIcon?: string): void;
65
+ /** Called while the player's cursor sits on this row. Chatty, subscribe deliberately. */
66
+ onHighlighted(handler: () => void): Unsubscribe;
67
+ protected signal<T extends unknown[]>(name: string): Signal<T>;
68
+ protected subscribeNodeEvent(name: string): void;
69
+ protected emit(op: UpdateOp): void;
70
+ protected setTextField(field: 'text' | 'description' | 'label' | 'lockedDescription' | 'confirmationDescription', value: TextLike | undefined, opName: string): void;
71
+ }
@@ -0,0 +1,96 @@
1
+ import type { Unsubscribe } from '../signal';
2
+ import { type TextLike } from '../text';
3
+ import { PluginItem } from './item';
4
+ import type { PluginMenu } from './menu';
5
+ /** A row the player presses. */
6
+ export declare class PluginButton extends PluginItem {
7
+ private readonly selected;
8
+ /** Called when the player presses the row. */
9
+ onSelected(handler: () => void): Unsubscribe;
10
+ }
11
+ /** A row that asks for a second press before it does anything. */
12
+ export declare class PluginConfirmButton extends PluginItem {
13
+ private confirmationDescriptionValue?;
14
+ private readonly confirmed;
15
+ /** What the row asks before its second press. Empty uses vMenu's own wording. */
16
+ get confirmationDescription(): TextLike | undefined;
17
+ set confirmationDescription(value: TextLike | undefined);
18
+ /** Called on the confirming second press, never on the first. */
19
+ onConfirmed(handler: () => void): Unsubscribe;
20
+ }
21
+ /** A row with a tick box. */
22
+ export declare class PluginCheckbox extends PluginItem {
23
+ private readonly changed;
24
+ /** Whether the state is saved in this resource's key value store and restored on start. */
25
+ get persisted(): boolean;
26
+ /** Whether the box is ticked. */
27
+ get checked(): boolean;
28
+ set checked(value: boolean);
29
+ /** Called when the player toggles the box. The new state is already in `checked`. */
30
+ onChanged(handler: (checked: boolean) => void): Unsubscribe;
31
+ private remember;
32
+ }
33
+ /** A row the player scrolls through a set of options on. */
34
+ export declare class PluginList extends PluginItem {
35
+ private readonly indexChanged;
36
+ private readonly selected;
37
+ /** The selected option, counted from 0. */
38
+ get selectedIndex(): number;
39
+ set selectedIndex(value: number);
40
+ /** Replaces the options, optionally moving the selection at the same time. */
41
+ setOptions(options: readonly TextLike[], selectedIndex?: number): void;
42
+ /** Called when the player scrolls the value. The new index is already in `selectedIndex`. */
43
+ onIndexChanged(handler: (oldIndex: number, newIndex: number) => void): Unsubscribe;
44
+ /** Called when the player presses the row, with the index they had selected. */
45
+ onSelected(handler: (index: number) => void): Unsubscribe;
46
+ protected raiseConfirmed(_index: number): void;
47
+ }
48
+ /** A list that asks for a second press before it does anything. */
49
+ export declare class PluginConfirmList extends PluginList {
50
+ private confirmationDescriptionValue?;
51
+ private readonly confirmed;
52
+ /** What the row asks before its second press. Empty uses vMenu's own wording. */
53
+ get confirmationDescription(): TextLike | undefined;
54
+ set confirmationDescription(value: TextLike | undefined);
55
+ /** Called on the confirming second press, with the index that was confirmed. */
56
+ onConfirmed(handler: (index: number) => void): Unsubscribe;
57
+ protected raiseConfirmed(index: number): void;
58
+ }
59
+ /** A row with a bar the player drags between a minimum and a maximum. */
60
+ export declare class PluginSlider extends PluginItem {
61
+ private readonly moved;
62
+ private readonly selected;
63
+ /** The lowest position. */
64
+ get min(): number;
65
+ /** The highest position. */
66
+ get max(): number;
67
+ /** Where the bar sits. */
68
+ get position(): number;
69
+ set position(value: number);
70
+ /** Called while the player drags the bar. The new position is already in `position`. */
71
+ onMoved(handler: (oldPosition: number, newPosition: number) => void): Unsubscribe;
72
+ /** Called when the player presses the row, with the position it sat at. */
73
+ onSelected(handler: (position: number) => void): Unsubscribe;
74
+ }
75
+ /** A row whose value your code works out each time the player scrolls it. */
76
+ export declare class PluginDynamicList extends PluginItem {
77
+ private readonly selected;
78
+ /**
79
+ * Produces the next value when the player scrolls: the current value and whether they went left.
80
+ * The answer crosses back to vMenu as an update, so it lands one beat after the press.
81
+ */
82
+ changeRequested?: (currentValue: string, left: boolean) => string | null | undefined;
83
+ /** The value the row shows. */
84
+ get value(): string;
85
+ set value(value: string);
86
+ /** Called when the player presses the row, with the value it showed. */
87
+ onSelected(handler: (value: string) => void): Unsubscribe;
88
+ }
89
+ /** A row that only shows text, used to split a menu into sections. */
90
+ export declare class PluginSeparator extends PluginItem {
91
+ }
92
+ /** A row that opens another menu. */
93
+ export declare class PluginSubmenu extends PluginItem {
94
+ /** The menu this row opens. Add its rows through this. */
95
+ readonly menu: PluginMenu;
96
+ }
@@ -0,0 +1,38 @@
1
+ import { type Unsubscribe } from '../signal';
2
+ import { type TextLike } from '../text';
3
+ import { type GateLike } from './gate';
4
+ import type { PluginItem } from './item';
5
+ /**
6
+ * Which row the cursor was on when a key was pressed. At most one of the two is set, and neither
7
+ * when the menu is empty or the row is not one of yours.
8
+ */
9
+ export interface PluginKeyPress {
10
+ /** The row under the cursor, when the player may use it. */
11
+ readonly item?: PluginItem;
12
+ /** The row under the cursor, when it is locked by its gate or disabled. Acting on it anyway is your call. */
13
+ readonly disabledItem?: PluginItem;
14
+ }
15
+ /**
16
+ * A key that works while its menu is open, shown as an instructional button at the bottom of the
17
+ * screen. The player can rebind it in the game's key settings, where it is listed under vMenu with
18
+ * your plugin's name in front.
19
+ */
20
+ export declare class PluginKey {
21
+ private readonly plugin;
22
+ private textValue?;
23
+ private gateValue?;
24
+ private readonly pressed;
25
+ /** The key's id, which also names the binding in the player's key settings. */
26
+ get id(): string;
27
+ /** The instructional button's label. Empty hides the button, the key still works. */
28
+ get text(): TextLike | undefined;
29
+ set text(value: TextLike | undefined);
30
+ /** A disabled key does nothing and shows no button. */
31
+ get enabled(): boolean;
32
+ set enabled(value: boolean);
33
+ /** While the gate fails the key does nothing and shows no button. */
34
+ get gate(): GateLike | undefined;
35
+ set gate(value: GateLike | undefined);
36
+ /** Called when the player presses the key, with the row the cursor was on. */
37
+ onPressed(handler: (press: PluginKeyPress) => void): Unsubscribe;
38
+ }