@amxts/menu-core 0.1.0

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/src/natives.ts ADDED
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Menu Core for Pawn plugins: the 30 mc_* natives of the original
3
+ * menu_core.amxx, as include/menu_core.inc declares them.
4
+ */
5
+ import { Player, plugin } from "@amxts/core";
6
+ import {
7
+ PawnFunction,
8
+ caller,
9
+ cellArrayRows,
10
+ cellsText,
11
+ colorTags,
12
+ createCellArray,
13
+ destroyCellArray,
14
+ menuColors,
15
+ pushCellArrayRow,
16
+ textCells,
17
+ } from "@amxts/core/kit";
18
+ import * as menus from "./index";
19
+ import { addNamedFilter, iniItem, insertItem, stateOf } from "./internal";
20
+ import { MenuShowOptions } from "./types";
21
+
22
+ plugin({ name: "Menu Core", version: "0.1.0", author: "kukson777", description: "Menus from ini files: the mc_* natives" });
23
+
24
+ /**
25
+ * Menu properties: mc_set_menu_property takes MP_LOCKED .. MP_HIDE_EXIT,
26
+ * mc_set_menu_property_string MP_ON_TIMEOUT .. MP_FILTER, and
27
+ * mc_get_menu_property_string MP_SECTION.
28
+ */
29
+ export enum MenuProperty {
30
+ MP_LOCKED = 0,
31
+ MP_GLOBAL_TIMER = 1,
32
+ MP_TIMER_DURATION = 2,
33
+ MP_HIDE_BACK = 3,
34
+ MP_HIDE_EXIT = 4,
35
+ // oxlint-disable-next-line typescript/no-duplicate-enum-values -- the include's own: 5 is read as the section and written as the timeout action (oxlint reports the first of the two)
36
+ MP_SECTION = 5,
37
+ MP_ON_TIMEOUT = 5,
38
+ MP_ACTIVE_ON = 6,
39
+ MP_FILTER = 7,
40
+ }
41
+
42
+ /** A list data source's item: 289 cells, laid out as menu_core.inc says. */
43
+ const ROW_CELLS = 289;
44
+ const PLACEHOLDER_CELLS = 256;
45
+
46
+ // What each native has registered: the numbers the originals return.
47
+ let conditionCount = 0;
48
+ let actionCount = 0;
49
+ let restrictionCount = 0;
50
+ let filterCount = 0;
51
+ let openCount = 0;
52
+ let closeCount = 0;
53
+ let showCount = 0;
54
+
55
+ /** A public of the plugin calling the native that is running; null when it has none of that name. */
56
+ function publicOf(name: string) {
57
+ return PawnFunction.find(caller(), name);
58
+ }
59
+
60
+ /** A data source fills an `Array:` of 289-cell items; one that does not return 1 is not used. */
61
+ function rowsFrom(fn: PawnFunction, viewer: Player) {
62
+ const items = createCellArray(ROW_CELLS);
63
+ const answered = fn.call().int(viewer.id).int(items).run();
64
+ const rows = answered == 1 ? cellArrayRows(items, ROW_CELLS).map(rowOf) : null;
65
+ destroyCellArray(items);
66
+ return rows;
67
+ }
68
+
69
+ /** [0] target, or -2 for a line of text; [1..64] action; [65..128] text; [129..160] restriction; [161..288] message. */
70
+ function rowOf(cells: number[]) {
71
+ const text = colorTags(cellsText(cells, 65, 64));
72
+ if (cells[0] == -2) return menus.textRow(text);
73
+ return menus.listRow(cells[0], text, cellsText(cells, 1, 64), cellsText(cells, 129, 32), colorTags(cellsText(cells, 161, 128)));
74
+ }
75
+
76
+ /** 1 or 0, as the original's natives answer. */
77
+ function answer(done: boolean) {
78
+ return done ? 1 : 0;
79
+ }
80
+
81
+ /**
82
+ * Registers a condition menus refer to - an item's condition, ACTIVE_ON, a
83
+ * FILTER. The first plugin to register a name answers for it (case-insensitive).
84
+ * Callback: public callback(id, viewerId, const name[]) - true when it holds.
85
+ * Returns the condition's id.
86
+ */
87
+ export function mc_register_condition(name: string, callback: string) {
88
+ const fn = publicOf(callback);
89
+ if (fn != null) menus.addCondition(name, (player, viewer, used) => fn.call().int(player.id).int(viewer.id).text(used).run() != 0);
90
+ return conditionCount++;
91
+ }
92
+
93
+ /**
94
+ * Registers an action run when an item is chosen. Built in, not registered:
95
+ * SHOW_<SECTION> opens that menu, CLOSE_MENU closes it; an action line may
96
+ * list several, space-separated. Names are case-sensitive; the first wins.
97
+ * Callback: public callback(id, const action[]) in an items menu,
98
+ * public callback(id, targetId) in a LIST_ menu. isCritical is accepted and
99
+ * does nothing. Returns the action's id.
100
+ */
101
+ export function mc_register_action(name: string, callback: string, _isCritical = false) {
102
+ const fn = publicOf(callback);
103
+
104
+ if (fn != null) {
105
+ menus.addAction(name, (player, target) => {
106
+ const menu = menus.activeMenu(player);
107
+ const call = fn.call().int(player.id);
108
+ // An items menu's action gets the action's name, a list menu's the row's target.
109
+ if (menu != null && menu.kind == "items") call.text(name);
110
+ else call.int(target);
111
+ call.run();
112
+ });
113
+ }
114
+
115
+ return actionCount++;
116
+ }
117
+
118
+ /**
119
+ * Registers a placeholder, %name% in titles and items, without the %.
120
+ * Callback: public callback(id, targetId, value[], len) - fills value[].
121
+ * A name registered again keeps the first. Returns the placeholder's id.
122
+ */
123
+ export function mc_register_placeholder(name: string, callback: string) {
124
+ const fn = publicOf(callback);
125
+ return menus.addPlaceholder(name, (player, target) => {
126
+ if (fn == null) return "";
127
+ const call = fn.call().int(player.id).int(target).buffer(PLACEHOLDER_CELLS).int(PLACEHOLDER_CELLS - 1);
128
+ call.run();
129
+ return colorTags(call.bufferText);
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Registers the config file's [section] as a menu. Returns the menu's id, or
135
+ * -1 when the section is missing, has no TITLE or no items, or is MAIN.
136
+ */
137
+ export function mc_register_menu(section: string) {
138
+ return menus.indexOf(menus.register(section));
139
+ }
140
+
141
+ /**
142
+ * Shows a menu. time: countdown seconds, -1 for the menu's TIME (or the
143
+ * countdown running). targetId: who a LIST_ menu is about. resetHistory
144
+ * starts the way back anew; forceOpen opens over a menu that holds on (a
145
+ * countdown, a lock); ignoreHistory leaves this menu out of the way back.
146
+ * Returns 1 when it opened; 0 when the player is not in the game, there is no
147
+ * such menu, a show filter or ACTIVE_ON stopped it.
148
+ */
149
+ export function mc_show_menu(player: Player, section: string, time = -1, targetId = 0, resetHistory = false, forceOpen = false, ignoreHistory = false) {
150
+ const options: MenuShowOptions = { target: targetId, resetHistory, force: forceOpen, skipHistory: ignoreHistory };
151
+ // -1, Pawn's "not given": the option left out.
152
+ if (time >= 0) options.time = time;
153
+ return answer(menus.show(player, section, options));
154
+ }
155
+
156
+ /**
157
+ * Sets the menu's shared countdown: starts one of `time` seconds when none
158
+ * runs, or changes the seconds left (0 stops it). Returns 1, or 0 when there
159
+ * is no such menu or nothing to change.
160
+ */
161
+ export function mc_set_menu_timer(section: string, time: number) {
162
+ const menu = menus.find(section);
163
+ return answer(menu != null && menu.setTimer(time));
164
+ }
165
+
166
+ /** Draws again every menu that uses the condition, for whoever looks at it. */
167
+ export function mc_notify_condition_changed(condition: string) {
168
+ menus.conditionChanged(condition);
169
+ }
170
+
171
+ /** Draws the menus again (space-separated names) for whoever looks at them. Returns how many were drawn. */
172
+ export function mc_refresh_menu(sections: string) {
173
+ return menus.refresh(sections);
174
+ }
175
+
176
+ /**
177
+ * Registers a restriction items name in their restriction column; "*"
178
+ * answers for every name no other restriction does. Callback:
179
+ * public callback(id, const restrictName[], targetId) - true when the player
180
+ * passes. message is said beside an item it greys out, when the item has no
181
+ * message of its own. Returns the restriction's id.
182
+ */
183
+ export function mc_register_restriction(name: string, callback: string, message?: string) {
184
+ const fn = publicOf(callback);
185
+ if (fn != null) menus.addRestriction(name, (player, used, target) => fn.call().int(player.id).text(used).int(target).run() != 0, colorTags(message ?? ""));
186
+ return restrictionCount++;
187
+ }
188
+
189
+ /**
190
+ * Greys out items whose action is actionName in menuSection while the
191
+ * callback says no; "" for either matches all. Callback:
192
+ * public callback(id, const menuSection[], const actionName[]). Returns 1.
193
+ */
194
+ export function mc_register_action_condition(menuSection: string, actionName: string, callback: string) {
195
+ const fn = publicOf(callback);
196
+ if (fn != null) menus.addActionCheck(menuSection, actionName, (player, menu, action) => fn.call().int(player.id).text(menu).text(action).run() != 0);
197
+ return 1;
198
+ }
199
+
200
+ /**
201
+ * Registers a filter over a condition someone else registered: it gets the
202
+ * condition's value and returns the one to use. Callback:
203
+ * public bool:callback(id, viewerId, const condition[], bool:currentVal).
204
+ * Returns the filter's id.
205
+ */
206
+ export function mc_register_condition_filter(condition: string, callback: string) {
207
+ const fn = publicOf(callback);
208
+ if (fn == null) return filterCount - 1;
209
+ menus.addConditionFilter(condition, (player, viewer, name, value) => fn.call().int(player.id).int(viewer.id).text(name).bool(value).run() != 0);
210
+ return filterCount++;
211
+ }
212
+
213
+ /**
214
+ * The rows of a LIST_ menu, instead of the players. Callback:
215
+ * public callback(id, Array:items) - pushes 289-cell items and returns 1:
216
+ * [0] the target the action gets (-2: a line of text), [1..64] an action of
217
+ * its own, [65..128] the text (%name%), [129..160] a restriction,
218
+ * [161..288] the message when it greys the row out. A second source for the
219
+ * menu replaces the first. Returns the source's id.
220
+ */
221
+ export function mc_register_list_data_source(menuName: string, callback: string) {
222
+ const fn = publicOf(callback);
223
+ return menus.setListSource(menuName, viewer => (fn != null ? rowsFrom(fn, viewer) : null));
224
+ }
225
+
226
+ /** Called when a menu opens. Callback: public callback(id, const section[]). Returns its id. */
227
+ export function mc_register_menu_open_callback(callback: string) {
228
+ const fn = publicOf(callback);
229
+ if (fn == null) return openCount - 1;
230
+ menus.addEventListener("open", event => fn.call().int(event.player.id).text(event.menu.name).run());
231
+ return openCount++;
232
+ }
233
+
234
+ /**
235
+ * Called when a menu closes - by a key, a timeout, another menu opening over
236
+ * it, the player leaving. Callback: public callback(id, const section[], bool:isTimeout).
237
+ * Returns its id.
238
+ */
239
+ export function mc_register_menu_close_callback(callback: string) {
240
+ const fn = publicOf(callback);
241
+ if (fn == null) return closeCount - 1;
242
+ menus.addEventListener("close", event => fn.call().int(event.player.id).text(event.menu.name).bool(event.timeout).run());
243
+ return closeCount++;
244
+ }
245
+
246
+ /**
247
+ * Asked before any menu opens; returning false stops it (mc_show_menu returns
248
+ * 0) - the place to tell the player why. Callback:
249
+ * public bool:callback(id, const section[]). Returns its id.
250
+ */
251
+ export function mc_register_show_filter(callback: string) {
252
+ const fn = publicOf(callback);
253
+ if (fn == null) return showCount - 1;
254
+ // The first filter that says no stops the menu; the ones after it are not asked.
255
+ menus.addEventListener("show", (event) => {
256
+ if (!event.defaultPrevented && fn.call().int(event.player.id).text(event.menu.name).run() == 0) event.preventDefault();
257
+ });
258
+ return showCount++;
259
+ }
260
+
261
+ /** Stops the menu's shared countdown and closes it for everyone. Returns 1, or 0 when none ran. */
262
+ export function mc_cancel_menu_timer(section: string) {
263
+ const menu = menus.find(section);
264
+ return answer(menu != null && menu.cancelTimer());
265
+ }
266
+
267
+ /** Locks (or unlocks) the player's menu: nothing can be chosen, and no other menu replaces it. */
268
+ export function mc_lock_menu(player: Player, lock = true) {
269
+ if (player.isConnected) menus.lock(player, lock);
270
+ }
271
+
272
+ /** Closes the player's menu. */
273
+ export function mc_hide_menu(player: Player) {
274
+ if (player.isConnected) menus.close(player);
275
+ }
276
+
277
+ /** Whether the player's menu is locked. */
278
+ export function mc_is_menu_locked(player: Player) {
279
+ return player.isConnected && menus.isLocked(player);
280
+ }
281
+
282
+ /** The id of the menu the player looks at, or -1. */
283
+ export function mc_get_active_menu(id: number) {
284
+ const player = new Player(id);
285
+ return player.isConnected ? menus.indexOf(menus.activeMenu(player)) : -1;
286
+ }
287
+
288
+ /**
289
+ * A string property of a menu by its id (mc_get_active_menu): MP_SECTION, the
290
+ * section name, into out[]. Returns its length; 0 when the id or the property
291
+ * is not one.
292
+ */
293
+ export function mc_get_menu_property_string(menuIdx: number, property: MenuProperty) {
294
+ const menu = menus.menuAt(menuIdx);
295
+ // 4 is what the original compared with; 5 is the include's MP_SECTION.
296
+ if (menu == null || (property != MenuProperty.MP_SECTION && property != 4)) return "";
297
+ return menu.name;
298
+ }
299
+
300
+ /**
301
+ * Sets MP_LOCKED, MP_GLOBAL_TIMER, MP_TIMER_DURATION, MP_HIDE_BACK or
302
+ * MP_HIDE_EXIT. Returns 1, or 0 when there is no such menu or property.
303
+ */
304
+ export function mc_set_menu_property(section: string, property: MenuProperty, value: number) {
305
+ const menu = menus.find(section);
306
+ if (menu == null) return 0;
307
+ const on = value != 0;
308
+ if (property == MenuProperty.MP_LOCKED) menu.locked = on;
309
+ else if (property == MenuProperty.MP_GLOBAL_TIMER) menu.sharedTimer = on;
310
+ else if (property == MenuProperty.MP_TIMER_DURATION) menu.time = value;
311
+ else if (property == MenuProperty.MP_HIDE_BACK) menu.hideBack = on;
312
+ else if (property == MenuProperty.MP_HIDE_EXIT) menu.hideExit = on;
313
+ else return 0;
314
+ return 1;
315
+ }
316
+
317
+ /**
318
+ * Sets MP_ON_TIMEOUT (the action when the countdown ends), MP_ACTIVE_ON (the
319
+ * condition the menu opens under) or MP_FILTER ("CONDITION|MESSAGE", added to
320
+ * a LIST_ menu's filters). Returns 1, or 0 when there is no such menu or
321
+ * property.
322
+ */
323
+ export function mc_set_menu_property_string(section: string, property: MenuProperty, value: string) {
324
+ const menu = menus.find(section);
325
+ if (menu == null) return 0;
326
+ if (property == MenuProperty.MP_ON_TIMEOUT) menu.onTimeout = value;
327
+ else if (property == MenuProperty.MP_ACTIVE_ON) menu.activeOn = value;
328
+ else if (property == MenuProperty.MP_FILTER) addFilter(menu, value);
329
+ else return 0;
330
+ return 1;
331
+ }
332
+
333
+ function addFilter(menu: menus.Menu, value: string) {
334
+ if (menu.kind != "list") return;
335
+ const pipe = value.indexOf("|");
336
+ if (pipe < 0) addNamedFilter(menu.name, value.trim(), "");
337
+ else addNamedFilter(menu.name, value.slice(0, pipe).trim(), colorTags(value.slice(pipe + 1).trim()));
338
+ }
339
+
340
+ /**
341
+ * Adds an item to a menu. name, condition and action take "A|B" variants: the
342
+ * first whose condition holds is shown. iPosition: its place among the items,
343
+ * -1 the end; emptyBefore/emptyAfter: blank lines around it. Returns 1, or 0
344
+ * when there is no such menu or the name gives no item.
345
+ */
346
+ export function mc_add_menu_item(section: string, name: string, placeholder?: string, condition?: string, action?: string, restriction?: string, restrictMsg?: string, iPosition = -1, emptyBefore = 0, emptyAfter = 0) {
347
+ const menu = menus.find(section);
348
+ if (menu == null) return 0;
349
+ if (!menus.hasText(name)) return 0;
350
+ const item = iniItem(colorTags(name), colorTags(placeholder ?? ""), condition ?? "", action ?? "", restriction ?? "", colorTags(restrictMsg ?? ""), -1);
351
+ item.spaceBefore = emptyBefore;
352
+ item.spaceAfter = emptyAfter;
353
+ insertItem(menu.name, item, iPosition);
354
+ return 1;
355
+ }
356
+
357
+ /**
358
+ * Adds an item that always takes slot 1-7 of every page. Returns 1, or 0 when
359
+ * there is no such menu or the name gives no item.
360
+ */
361
+ export function mc_add_fixed_menu_item(section: string, slot: number, name: string, placeholder?: string, action?: string, condition?: string, restriction?: string, emptyBefore = 0, emptyAfter = 0) {
362
+ const menu = menus.find(section);
363
+ if (menu == null) return 0;
364
+ if (!menus.hasText(name)) return 0;
365
+ const item = iniItem(colorTags(name), colorTags(placeholder ?? ""), condition ?? "", action ?? "", restriction ?? "", "", slot - 1);
366
+ item.spaceBefore = emptyBefore;
367
+ item.spaceAfter = emptyAfter;
368
+ stateOf(menu.name).fixed.push(item);
369
+ return 1;
370
+ }
371
+
372
+ /**
373
+ * Adds a line of text - a header, a separator - to the items a data source
374
+ * fills; call it from the data source callback. Returns 1.
375
+ */
376
+ export function mc_add_list_text(aItems: number, text: string, centered = false) {
377
+ const row = menus.textRow(text, centered);
378
+ const cells = [-2].concat(textCells("", 64)).concat(textCells(row.text, 64)).concat(textCells("", 32)).concat(textCells("", 128));
379
+ pushCellArrayRow(aItems, cells);
380
+ return 1;
381
+ }
382
+
383
+ /** Removes every item of a menu, fixed ones too. Returns 1, or 0 when there is no such menu. */
384
+ export function mc_clear_menu_items(section: string) {
385
+ const menu = menus.find(section);
386
+ if (menu != null) menu.clearItems();
387
+ return answer(menu != null);
388
+ }
389
+
390
+ /**
391
+ * Makes a menu in code, without a config section; a name starting with LIST_
392
+ * makes a list menu. Returns 1, or 0 when a menu of that name is there.
393
+ */
394
+ export function mc_create_menu(section: string, title: string) {
395
+ if (menus.find(section) != null) return 0;
396
+ menus.create(section, { title: colorTags(title) });
397
+ return 1;
398
+ }
399
+
400
+ /** The page, from 0, the player's menu is drawn at next. */
401
+ export function mc_set_menu_page(player: Player, page: number) {
402
+ if (player.isConnected) menus.setPage(player, page);
403
+ }
404
+
405
+ /**
406
+ * The text the player's menu shows at the moment, colour codes and all (""
407
+ * when none is open) - for a test or a log to see what the player sees.
408
+ */
409
+ export function mc_get_menu_text(player: Player) {
410
+ return player.isConnected ? menuColors(menus.shownText(player)) : "";
411
+ }
package/src/types.ts ADDED
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The types of Menu Core's API: options, rows and the callbacks plugins
3
+ * register.
4
+ */
5
+ import { Player } from "@amxts/core";
6
+
7
+ /** A menu's kind, one of `"items"` (a list of items) or `"list"` (a row per player, or per row of a list source). */
8
+ export type MenuKind = "items" | "list";
9
+
10
+ /**
11
+ * Text of a menu - a title, an item, a message: the text itself, or a
12
+ * function that gives it for the player who looks; `target` is the row's
13
+ * in a list menu, else the menu's. A lang key is translated either way.
14
+ * Colour tags are chat's letters: `!y` yellow, `!r` red, `!d` grey, `!w`
15
+ * white, `!R` to the right edge; chat's own `!g`, `!b` and `!t` are dropped.
16
+ *
17
+ * menus.create("SHOP", { title: (player) => `Shop for ${player.name}` });
18
+ */
19
+ export type MenuText = string | ((player: Player, target: number) => string);
20
+
21
+ /** A row of a list menu, as a list source gives it - made with `listRow()` or `textRow()`. */
22
+ export interface ListRow {
23
+ /** The row's kind, one of `"item"` (a row to choose) or `"text"` (a line of text, not a choice). */
24
+ kind: "item" | "text";
25
+ /** The row's target, handed to the action: e.g. a player's `id`, an entity or an index of the source's own; `0` for a line of text. */
26
+ target: number;
27
+ /** The row's text, put for `%name%` in the menu's row template. */
28
+ text: string;
29
+ /** Action names of the row's own, run instead of the template's; `""` for the template's. */
30
+ action: string;
31
+ /** Restriction names, space-separated: the row is greyed out unless each passes. */
32
+ restriction: string;
33
+ /** The text beside the row while it is greyed out; `""` for the restriction's own message. */
34
+ restrictionMessage: string;
35
+ }
36
+
37
+ /**
38
+ * The options of a menu made with `create()`. Every field may be left out:
39
+ *
40
+ * menus.create("SHOP", { title: "Shop", time: 30, activeWhen: player => player.isAlive });
41
+ */
42
+ export interface MenuOptions {
43
+ /** The menu's title: the text - a lang key too - or a function that gives it for the player; left out, the menu's name. */
44
+ title?: MenuText;
45
+ /** Seconds on the countdown when the menu opens, e.g. `10`; left out, none. */
46
+ time?: number;
47
+ /** Hiding of the `"Back"` button: `true` leaves it out. */
48
+ hideBack?: boolean;
49
+ /** Hiding of the `"Exit"` button: `true` leaves it out. */
50
+ hideExit?: boolean;
51
+ /** A lock on the menu: while `true`, items cannot be chosen and no other menu replaces this one. */
52
+ locked?: boolean;
53
+ /** A test the menu opens under: while it says no to the player, the menu does not open for him. */
54
+ activeWhen?: (player: Player) => boolean;
55
+ }
56
+
57
+ /**
58
+ * The options of showing a menu. Every field may be left out:
59
+ *
60
+ * shop.show(player, { time: 10 });
61
+ */
62
+ export interface MenuShowOptions {
63
+ /** Seconds on the countdown; left out, the countdown running goes on, or the menu's own starts. */
64
+ time?: number;
65
+ /** The player the menu is about, by `id`: `%target%`, and the target an action gets; `0` when left out. */
66
+ target?: number;
67
+ /** A new way back: `true` forgets the menus this one was opened from. */
68
+ resetHistory?: boolean;
69
+ /** Opening over a menu that holds on - a countdown, a lock: `true` opens anyway. */
70
+ force?: boolean;
71
+ /** Leaving the menu out of the way back: `true` does not remember it. */
72
+ skipHistory?: boolean;
73
+ }
74
+
75
+ /**
76
+ * The options of an item, besides its text. Every field may be left out:
77
+ *
78
+ * shop.addItem("Heal", {
79
+ * visible: player => player.health < 100,
80
+ * onSelect: (player) => { player.health = 100; },
81
+ * });
82
+ */
83
+ export interface MenuItemOptions {
84
+ /** The function run when the item is chosen: the player who chose it, and the target - the row's in a list menu, else the menu's. */
85
+ onSelect?: (player: Player, target: number) => void;
86
+ /** A test the item is shown under: while it says no, the item is left out and takes no slot. */
87
+ visible?: (player: Player, target: number) => boolean;
88
+ /**
89
+ * A test the item can be chosen under - while it says no, the item is
90
+ * greyed out with `message` beside it; or a list of requirements, each with
91
+ * a message of its own - the first that fails gives its message.
92
+ *
93
+ * enabled: [
94
+ * { when: (player) => player.frags >= 5, message: "5 frags needed" },
95
+ * { when: (player) => player.armor < 100, message: (player) => `(${player.armor} already)` },
96
+ * ],
97
+ */
98
+ enabled?: ((player: Player, target: number) => boolean) | Requirement[];
99
+ /** The text beside the item while `enabled` greys it out, for a requirement without a message of its own: the text, e.g. `"(full)"`, or a function that gives it for the player. */
100
+ message?: MenuText;
101
+ /** The text after the item's name, for items of menu files and Pawn plugins, placeholders and all, e.g. `"%hp%"`; in code the item's text is a function instead. */
102
+ placeholder?: string;
103
+ /** Action names from `addAction()` run when the item is chosen, or a built-in one: `"SHOW_<MENU>"`, `"CLOSE_MENU"`. */
104
+ action?: string;
105
+ /** The item's place among the items, from `0`; left out, the end. */
106
+ at?: number;
107
+ /** Blank lines before the item. */
108
+ spaceBefore?: number;
109
+ /** Blank lines after the item. */
110
+ spaceAfter?: number;
111
+ }
112
+
113
+ /**
114
+ * A requirement of an item, in the list `enabled` takes: while `when` says
115
+ * no, the item is greyed out with `message` beside it.
116
+ *
117
+ * { when: (player) => player.frags >= 5, message: "5 frags needed" }
118
+ */
119
+ export interface Requirement {
120
+ /** A test the requirement holds under: the player who looks, and the target - the row's in a list menu, else the menu's. */
121
+ when: (player: Player, target: number) => boolean;
122
+ /** The text beside the item while `when` says no: the text, a lang key, or a function that gives it for the player; left out, the item's `message`. */
123
+ message?: MenuText;
124
+ }
125
+
126
+ /** A condition's test, as `addCondition()` registers it. In a list menu `player` is the row's player and `viewer` whoever looks. */
127
+ export type ConditionTest = (player: Player, viewer: Player, name: string) => boolean;
128
+ /** An action, as `addAction()` registers it: `target` is the row's in a list menu, else the menu's. */
129
+ export type ActionHandler = (player: Player, target: number, name: string) => void;
130
+ /** A placeholder's value: the text `%name%` stands for. */
131
+ export type PlaceholderValue = (player: Player, target: number, name: string) => string;
132
+ /** A restriction's test, as `addRestriction()` registers it; `name` is the whole token, `"NAME:param"` included. */
133
+ export type RestrictionTest = (player: Player, name: string, target: number) => boolean;
134
+ /** A test of an item's action, as `addActionCheck()` registers it: `false` greys the item out. */
135
+ export type ActionTest = (player: Player, menu: string, action: string) => boolean;
136
+ /** A filter over a condition someone else registered: gets its value and returns the one to use. */
137
+ export type ConditionFilter = (player: Player, viewer: Player, name: string, value: boolean) => boolean;
138
+ /** A test of a row of a list menu, as `addFilter()` takes it: `player` is the row's player, `viewer` whoever looks. */
139
+ export type RowTest = (player: Player, viewer: Player) => boolean;
140
+ /** A list source: the rows of a list menu for the player who looks; `null` lists the players instead. */
141
+ export type ListSource = (viewer: Player, menu: string) => ListRow[] | null;
142
+
143
+ /** A menu event's type, one of `"open"` and `"close"` as they happen, or `"show"` before a menu opens, to stop it. */
144
+ export type MenuEventType = "open" | "close" | "show";
145
+
146
+ /** Menu Core's options: `menus` in `amxts.config.ts`. */
147
+ export interface MenuCoreOptions {
148
+ /** The menu file, from `configs/`: e.g. `"menu"` is `configs/menu.ini`, `menu.yaml`, `menu.yml`, `menu.json` or `menu.jsonc` - the first that is there; `"myserver/menu.yaml"` is that file. */
149
+ file: string;
150
+ /** The file read instead when `file` is empty or not there, e.g. `"menu"` for `configs/menu.ini` or `menu.yaml`; `""` is none. */
151
+ fallback: string;
152
+ }
153
+
154
+ declare module "@amxts/core" {
155
+ interface ModuleOptions {
156
+ menus?: Partial<MenuCoreOptions>;
157
+ }
158
+ }