@amxts/menu-core 0.1.0 → 0.2.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/PAWN.md +4 -2
- package/PAWN.ru.md +4 -2
- package/README.md +69 -27
- package/README.ru.md +70 -28
- package/package.json +3 -3
- package/prebuilt/linux/menu-core.aot +0 -0
- package/prebuilt/manifest.json +19 -11
- package/prebuilt/windows/menu-core.aot +0 -0
- package/scripts/docs/src/{index.ts → menus.ts} +35 -21
- package/scripts/docs/src/types.ts +102 -52
- package/src/index.ts +3 -1598
- package/src/internal.ts +350 -328
- package/src/menus.ts +1744 -0
- package/src/natives.ts +11 -11
- package/src/types.ts +69 -39
package/PAWN.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[English](PAWN.md) | [Русский](PAWN.ru.md)
|
|
4
4
|
|
|
5
|
-
Menu Core serves the 30 natives of `menu_core.inc` (`mc_register_action`, `mc_show_menu`, `mc_add_menu_item` and the rest) with their signatures, so compiled `.amxx` plugins work unchanged.
|
|
5
|
+
Menu Core serves the 30 natives of `menu_core.inc` (`mc_register_action`, `mc_show_menu`, `mc_add_menu_item` and the rest) with their signatures, so compiled `.amxx` plugins work unchanged.
|
|
6
|
+
|
|
7
|
+
Pawn plugins write `#include <menu_core>`; the package ships `include/menu_core.inc`. Only one plugin on a server can give these natives: if another Pawn plugin in `plugins.ini` registers `mc_*` natives too, comment it out.
|
|
6
8
|
|
|
7
9
|
Callbacks are named by their public function, and Menu Core calls them in the plugin that registered them.
|
|
8
10
|
|
|
@@ -16,7 +18,7 @@ A plugin compiled against it gets a tag warning for a bare number where `MenuPro
|
|
|
16
18
|
|
|
17
19
|
### How the natives behave
|
|
18
20
|
|
|
19
|
-
- No
|
|
21
|
+
- No length limits: names, titles and placeholders are as long as they are written, a menu keeps every item, the way back is as long as it gets, and a menu longer than 500 bytes is sent whole.
|
|
20
22
|
- `mc_get_menu_property_string(idx, MP_SECTION)` gives the menu's section.
|
|
21
23
|
- A condition filter applies wherever the condition is asked.
|
|
22
24
|
- A restriction's `message` is shown beside an item it greys out when the item has no message of its own.
|
package/PAWN.ru.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[English](PAWN.md) | [Русский](PAWN.ru.md)
|
|
4
4
|
|
|
5
|
-
Menu Core отдаёт 30 нативов из `menu_core.inc` (`mc_register_action`, `mc_show_menu`, `mc_add_menu_item` и остальные) с их сигнатурами, так что собранные `.amxx`-плагины работают без изменений.
|
|
5
|
+
Menu Core отдаёт 30 нативов из `menu_core.inc` (`mc_register_action`, `mc_show_menu`, `mc_add_menu_item` и остальные) с их сигнатурами, так что собранные `.amxx`-плагины работают без изменений.
|
|
6
|
+
|
|
7
|
+
Pawn-плагины пишут `#include <menu_core>`; в пакете лежит `include/menu_core.inc`. Отдавать эти нативы на сервере может только один плагин: если в `plugins.ini` есть другой Pawn-плагин с нативами `mc_*`, закомментируйте его.
|
|
6
8
|
|
|
7
9
|
Колбэки задаются именем публичной функции, и Menu Core вызывает их в том плагине, который их зарегистрировал.
|
|
8
10
|
|
|
@@ -16,7 +18,7 @@ Menu Core отдаёт 30 нативов из `menu_core.inc` (`mc_register_acti
|
|
|
16
18
|
|
|
17
19
|
### Как работают нативы
|
|
18
20
|
|
|
19
|
-
- Нет ограничений
|
|
21
|
+
- Нет ограничений длины: названия, заголовки и подстановки любой длины, меню хранит все пункты, путь назад любой длины, меню длиннее 500 байт отправляется целиком.
|
|
20
22
|
- `mc_get_menu_property_string(idx, MP_SECTION)` отдаёт секцию меню.
|
|
21
23
|
- Фильтр условия применяется везде, где спрашивают условие.
|
|
22
24
|
- `message` ограничения показывается рядом с посеревшим пунктом, если у пункта нет своего сообщения.
|
package/README.md
CHANGED
|
@@ -17,20 +17,17 @@
|
|
|
17
17
|
|
|
18
18
|
Describe a menu once, in a file — INI, YAML or JSON — or in code, and Menu Core handles the rest: pages, keys, the way back, countdowns, and items that appear, disappear or grey out depending on who is looking.
|
|
19
19
|
|
|
20
|
-
> [!WARNING]
|
|
21
|
-
> **In progress.** Menu Core has been tried in game only a few times, and its API may still change.
|
|
22
|
-
|
|
23
20
|
## Features
|
|
24
21
|
|
|
25
22
|
- **Menus from a file or from code.** Admins edit `menu.ini`, `menu.yaml` or `menu.json` without touching a plugin; plugins add their own items at run time.
|
|
26
23
|
- **Mistakes said where they are.** An unknown key, a value of the wrong kind, a condition or an action nobody registered: the server console says so, with the file and the line.
|
|
27
24
|
- **Shown, and chosen.** An item says when it is shown at all (`visible`) and when it can be chosen (`enabled`): greyed out, with the message of the first requirement that fails beside it.
|
|
28
|
-
- **Text that follows the player.** A title, an item or a message can be a function — ``(player) => `Heal (${player.health} HP)` `` — read each time the menu is drawn; in `menu.ini`, `%hp%`-style placeholders do the same.
|
|
25
|
+
- **Text that follows the player.** A title, an item or a message can be a function — ``({ player }) => `Heal (${player.health} HP)` `` — read each time the menu is drawn; in `menu.ini`, `%hp%`-style placeholders do the same.
|
|
29
26
|
- **List menus.** A row per player or per item of your own list, with filters and a message when nothing is left.
|
|
30
27
|
- **Variants.** One item, several faces: the first variant whose `when` holds is shown.
|
|
31
28
|
- **Countdowns and locks.** Timed menus, one timer per player or one for everyone, and menus that cannot be closed or replaced.
|
|
32
29
|
- **One instance per server.** Every plugin, TypeScript or Pawn, fills and opens the same menus.
|
|
33
|
-
- **No
|
|
30
|
+
- **No length limits.** Long names, many items, Cyrillic menus over 500 bytes.
|
|
34
31
|
|
|
35
32
|
## Installation
|
|
36
33
|
|
|
@@ -65,38 +62,72 @@ The build loads Config Core first. A config that lists only Menu Core builds the
|
|
|
65
62
|
A plugin uses the module as `menus`, without an import line: the build adds the import to the plugins that use it, and builds the module only when some plugin does.
|
|
66
63
|
|
|
67
64
|
```ts
|
|
68
|
-
const shop = menus.create("SHOP", { title: (player) => `Shop for ${player.name}` });
|
|
65
|
+
const shop = menus.create("SHOP", { title: ({ player }) => `Shop for ${player.name}` });
|
|
69
66
|
|
|
70
|
-
shop.addItem(
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
shop.addItem({
|
|
68
|
+
title: ({ player }) => `Heal (${player.health} HP)`,
|
|
69
|
+
visible: ({ player }) => player.health < 100,
|
|
70
|
+
onSelect: ({ player }) => {
|
|
73
71
|
player.health = 100;
|
|
74
72
|
},
|
|
75
73
|
});
|
|
76
|
-
shop.addItem(
|
|
74
|
+
shop.addItem({
|
|
75
|
+
title: "Buy AWP",
|
|
77
76
|
// Greyed out while one says no: the first that does gives its message.
|
|
78
77
|
enabled: [
|
|
79
|
-
{ when: (player) => player.isAlive, message: "Only while alive" },
|
|
80
|
-
{ when: (player) => player.
|
|
78
|
+
{ when: ({ player }) => player.isAlive, message: "Only while alive" },
|
|
79
|
+
{ when: ({ player }) => player.money >= 4750, message: ({ player }) => `Need $${4750 - player.money} more` },
|
|
81
80
|
],
|
|
82
|
-
onSelect: (player) => {
|
|
83
|
-
player.
|
|
81
|
+
onSelect: ({ player }) => {
|
|
82
|
+
player.money = player.money - 4750;
|
|
84
83
|
player.give("weapon_awp");
|
|
85
84
|
},
|
|
86
85
|
});
|
|
87
|
-
shop.addItem(
|
|
88
|
-
|
|
86
|
+
shop.addItem({
|
|
87
|
+
title: "Reset score",
|
|
88
|
+
enabled: ({ player }) => player.frags != 0,
|
|
89
89
|
message: "(already 0)",
|
|
90
|
-
onSelect: (player) => {
|
|
90
|
+
onSelect: ({ player }) => {
|
|
91
91
|
player.frags = 0;
|
|
92
92
|
},
|
|
93
93
|
});
|
|
94
|
-
shop.addItem("Close",
|
|
94
|
+
shop.addItem({ title: "Close", action: "CLOSE_MENU", spaceBefore: 1 });
|
|
95
|
+
|
|
96
|
+
server.addCommand("/shop", ({ player }) => shop.show(player));
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every function of a menu — a title, `visible`, `enabled`, `onSelect` — gets one object, the menu's context:
|
|
100
|
+
|
|
101
|
+
- `player` — the player the menu is shown to: who looks at it, and who chooses;
|
|
102
|
+
- `target` — the player the menu is about: the row's in a list menu, the one `show(player, { target })` was given otherwise, and `player` himself when there is none;
|
|
103
|
+
- `row` — the row's number in a list menu, as `listRow()` gave it: an entity, an index of your own list — or a player's `id` in a list of players;
|
|
104
|
+
- `menu` — the menu.
|
|
105
|
+
|
|
106
|
+
A list menu (its name starts with `LIST_`) has a row per player, drawn with its first item:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const kick = menus.create("LIST_KICK", { title: "Kick a player" });
|
|
110
|
+
kick.addFilter(({ player, target }) => target.id != player.id, "Nobody to kick");
|
|
111
|
+
kick.addItem({
|
|
112
|
+
title: ({ target }) => target.name,
|
|
113
|
+
onSelect: ({ player, target }) => target.kick(`Kicked by ${player.name}`),
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Or a row per item of a list of your own, `menu.setListSource(rows)`:
|
|
95
118
|
|
|
96
|
-
|
|
119
|
+
```ts
|
|
120
|
+
const maps = ["de_dust2", "de_inferno", "de_nuke"];
|
|
121
|
+
|
|
122
|
+
const vote = menus.create("LIST_MAPS", { title: "Next map" });
|
|
123
|
+
vote.setListSource(() => maps.map((map, index) => menus.listRow(index, map)));
|
|
124
|
+
vote.addItem({
|
|
125
|
+
title: ({ row }) => maps[row],
|
|
126
|
+
onSelect: ({ player, row }) => print(0, `${player.name} votes for ${maps[row]}`),
|
|
127
|
+
});
|
|
97
128
|
```
|
|
98
129
|
|
|
99
|
-
Text — a title, an item, a message — is the text itself or a function that gives it for the
|
|
130
|
+
Text — a title, an item, a message — is the text itself or a function that gives it for the context. A plain string that is a lang key is translated for the player.
|
|
100
131
|
|
|
101
132
|
An item says when it is shown and when it can be chosen:
|
|
102
133
|
|
|
@@ -115,13 +146,14 @@ A menu is an object: `menus.create()` makes one, and its methods fill and open i
|
|
|
115
146
|
|
|
116
147
|
| Method | What it does |
|
|
117
148
|
| --- | --- |
|
|
118
|
-
| `menu.addItem(
|
|
119
|
-
| `menu.addFixedItem(slot,
|
|
120
|
-
| `menu.addFilter(test, message?)` | A list menu leaves out the rows `test` says no to. |
|
|
121
|
-
| `menu.setListSource(rows)` | A list menu's own rows: `listRow(
|
|
149
|
+
| `menu.addItem(item)` | Adds an item: `title` — the text, or a function of the context — and `onSelect`, `visible` (left out while it says no), `enabled` (greyed out while it says no — a test, or a list of `{ when, message }`), `message`, `at`, `spaceBefore`, `spaceAfter`; and for menus that name what plugins register, `action` and `placeholder`. |
|
|
150
|
+
| `menu.addFixedItem(slot, item)` | An item that keeps slot 1–7 on every page. |
|
|
151
|
+
| `menu.addFilter(test, message?)` | A list menu leaves out the rows `test` says no to; `target` is the row's player. |
|
|
152
|
+
| `menu.setListSource(rows)` | A list menu's own rows: `listRow(row, text)`, `textRow(text)`. |
|
|
122
153
|
| `menu.addEventListener("open" \| "close" \| "show", listener)` | This menu's events; `"show"` comes before it opens, `event.preventDefault()` stops it. |
|
|
123
154
|
| `menu.show(player, options?)` | Opens the menu; `false` when it does not open. Options: `time`, `target`, `resetHistory`, `force`, `skipHistory`. |
|
|
124
155
|
| `menu.refresh()` · `menu.close()` · `menu.clearItems()` | Redraw it or close it for whoever looks at it; remove its items. |
|
|
156
|
+
| `menu.runActions(player, line, target?)` | Runs an action line — `"GIVE_HP CLOSE_MENU"` — as a choice in the menu does. |
|
|
125
157
|
| `menu.setTimer(seconds)` · `menu.cancelTimer()` | The countdown everyone looking at it shares. |
|
|
126
158
|
|
|
127
159
|
Its fields — `title`, `time`, `hideBack`, `hideExit`, `locked`, `sharedTimer` — are set directly; `name`, `kind` and `countdown` are read.
|
|
@@ -131,7 +163,7 @@ Its fields — `title`, `time`, `hideBack`, `hideExit`, `locked`, `sharedTimer`
|
|
|
131
163
|
| `create(name, options?)` | A menu in code, or the existing one with that name. A name starting with `LIST_` makes a list menu. Options: `title` (text or a function), `time`, `hideBack`, `hideExit`, `locked`, `activeWhen`. |
|
|
132
164
|
| `find(name)` · `register(name)` | A menu by its name; `register` reads it from the file ahead of time. |
|
|
133
165
|
| `show(player, name, options?)` · `close(player)` · `activeMenu(player)` · `lock(player)` | The player's menu, whichever it is. |
|
|
134
|
-
| `addCondition(name, test)` · `addAction(name, handler)` · `addPlaceholder(name, value)` · `addRestriction(name, test, message?)` | What menu files and Pawn plugins name, answered by functions — `%name%` in their text is a placeholder. A restriction's `message` is said beside an item it greys out, unless the item or the requirement has its own. `menu.addPlaceholder(name, value)` gives one to a single menu. |
|
|
166
|
+
| `addCondition(name, test)` · `addAction(name, handler)` · `addPlaceholder(name, value)` · `addRestriction(name, test, message?)` | What menu files and Pawn plugins name, answered by functions — `%name%` in their text is a placeholder. An action, a placeholder and a restriction get the menu's context and the `name` they are asked by; a condition, `(player, viewer, name)`, is asked of the row's player in a list menu. A restriction's `message` is said beside an item it greys out, unless the item or the requirement has its own. `menu.addPlaceholder(name, value)` gives one to a single menu. |
|
|
135
167
|
| `setListSource(name, rows)` · `refresh("A B")` · `conditionChanged(name)` · `addEventListener(type, listener)` | The same for menus by name, and every menu's events. |
|
|
136
168
|
|
|
137
169
|
## Menus in a file
|
|
@@ -333,14 +365,24 @@ What does not fit a menu file is said in the server console with the file and th
|
|
|
333
365
|
|
|
334
366
|
### In the editor
|
|
335
367
|
|
|
336
|
-
The amxts extension for VS Code checks a menu file as you type — the same checks, in the same words — and completes the keys and the names your plugins register (TypeScript, Pawn and installed modules), with hover and go to definition to the registration. A name added in a plugin is offered in the menu file at once, before the plugin is saved. It is
|
|
368
|
+
The amxts extension for VS Code checks a menu file as you type — the same checks, in the same words — and completes the keys and the names your plugins register (TypeScript, Pawn and installed modules), with hover and go to definition to the registration. A name added in a plugin is offered in the menu file at once, before the plugin is saved. It is installed from its `.vsix`, with `code --install-extension amxts-vscode-<version>.vsix`.
|
|
337
369
|
|
|
338
370
|
> [!WARNING]
|
|
339
371
|
> The extension knows only names written as a string in the workspace: `menus.addAction(name, ...)` with the name in a variable, and a name only a Pawn plugin on the server registers, are a warning in the editor. The server's check on its first frame is the one that counts.
|
|
340
372
|
|
|
341
373
|
## Pawn plugins
|
|
342
374
|
|
|
343
|
-
Pawn
|
|
375
|
+
A Pawn plugin makes, fills and shows menus through Menu Core too, with its 30 natives whose names start with `mc_`: `mc_register_action`, `mc_show_menu`, `mc_add_menu_item` and the rest. The package ships their include in `include/`:
|
|
376
|
+
|
|
377
|
+
```pawn
|
|
378
|
+
#include <menu_core>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
A Pawn plugin already compiled against this include works as it is, with nothing to rebuild.
|
|
382
|
+
|
|
383
|
+
Only one plugin on a server can give these natives: if another Pawn plugin in `plugins.ini` registers `mc_*` natives too, comment it out.
|
|
384
|
+
|
|
385
|
+
Menu Core runs on the server as one of the project's plugins. When no TypeScript plugin of the project uses it, keep it in the build for the Pawn plugins: `pawn: ["@amxts/menu-core"]` in `amxts.config.ts`. Every native with its signature: [PAWN.md](PAWN.md).
|
|
344
386
|
|
|
345
387
|
## Testing
|
|
346
388
|
|
package/README.ru.md
CHANGED
|
@@ -17,20 +17,17 @@
|
|
|
17
17
|
|
|
18
18
|
Опишите меню один раз — в файле INI, YAML или JSON или в коде, — а остальное Menu Core сделает сам: страницы, клавиши, возврат назад, обратный отсчёт и пункты, которые появляются, скрываются или становятся серыми в зависимости от того, кто смотрит.
|
|
19
19
|
|
|
20
|
-
> [!WARNING]
|
|
21
|
-
> **В работе.** В игре Menu Core пробовали всего несколько раз, и API ещё может измениться.
|
|
22
|
-
|
|
23
20
|
## Возможности
|
|
24
21
|
|
|
25
22
|
- **Меню из файла или из кода.** Админ правит `menu.ini`, `menu.yaml` или `menu.json`, не трогая плагин; плагины добавляют свои пункты на ходу.
|
|
26
23
|
- **Ошибки — с местом.** Незнакомый ключ, значение не того вида, условие или действие, которое никто не зарегистрировал: консоль сервера говорит об этом, с файлом и строкой.
|
|
27
24
|
- **Виден и доступен.** Пункт говорит, когда он вообще показан (`visible`) и когда его можно выбрать (`enabled`): иначе он серый, а рядом — сообщение первого требования, которое не выполнено.
|
|
28
|
-
- **Текст, который зависит от игрока.** Заголовок, пункт или сообщение могут быть функцией — ``(player) => `Лечение (${player.health} HP)` `` — она читается при каждой отрисовке; в `menu.ini` то же делают плейсхолдеры вроде `%hp%`.
|
|
25
|
+
- **Текст, который зависит от игрока.** Заголовок, пункт или сообщение могут быть функцией — ``({ player }) => `Лечение (${player.health} HP)` `` — она читается при каждой отрисовке; в `menu.ini` то же делают плейсхолдеры вроде `%hp%`.
|
|
29
26
|
- **Меню-списки.** Строка на каждого игрока или на каждый элемент своего списка, с фильтрами и сообщением, если никого не осталось.
|
|
30
27
|
- **Варианты.** Один пункт, несколько видов: показывается первый вариант, чьё `when` выполняется.
|
|
31
28
|
- **Отсчёт и блокировка.** Меню с таймером — свой у каждого игрока или один на всех — и меню, которое нельзя закрыть или заменить.
|
|
32
29
|
- **Один экземпляр на сервер.** Все плагины, на TypeScript и на Pawn, наполняют и открывают одни и те же меню.
|
|
33
|
-
- **Без ограничений
|
|
30
|
+
- **Без ограничений длины.** Длинные названия, сколько угодно пунктов, меню на кириллице длиннее 500 байт.
|
|
34
31
|
|
|
35
32
|
## Установка
|
|
36
33
|
|
|
@@ -65,38 +62,72 @@ export default defineConfig({
|
|
|
65
62
|
Плагин пользуется модулем как `menus`, без строки импорта: сборка добавляет импорт в те плагины, которые им пользуются, и собирает модуль, только когда им пользуется хоть один.
|
|
66
63
|
|
|
67
64
|
```ts
|
|
68
|
-
const shop = menus.create("SHOP", { title: (player) => `Магазин для ${player.name}` });
|
|
65
|
+
const shop = menus.create("SHOP", { title: ({ player }) => `Магазин для ${player.name}` });
|
|
69
66
|
|
|
70
|
-
shop.addItem(
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
shop.addItem({
|
|
68
|
+
title: ({ player }) => `Лечение (${player.health} HP)`,
|
|
69
|
+
visible: ({ player }) => player.health < 100,
|
|
70
|
+
onSelect: ({ player }) => {
|
|
73
71
|
player.health = 100;
|
|
74
72
|
},
|
|
75
73
|
});
|
|
76
|
-
shop.addItem(
|
|
74
|
+
shop.addItem({
|
|
75
|
+
title: "Купить AWP",
|
|
77
76
|
// Серый, пока одно отвечает «нет»: первое такое даёт своё сообщение.
|
|
78
77
|
enabled: [
|
|
79
|
-
{ when: (player) => player.isAlive, message: "Только живым" },
|
|
80
|
-
{ when: (player) => player.
|
|
78
|
+
{ when: ({ player }) => player.isAlive, message: "Только живым" },
|
|
79
|
+
{ when: ({ player }) => player.money >= 4750, message: ({ player }) => `Не хватает $${4750 - player.money}` },
|
|
81
80
|
],
|
|
82
|
-
onSelect: (player) => {
|
|
83
|
-
player.
|
|
81
|
+
onSelect: ({ player }) => {
|
|
82
|
+
player.money = player.money - 4750;
|
|
84
83
|
player.give("weapon_awp");
|
|
85
84
|
},
|
|
86
85
|
});
|
|
87
|
-
shop.addItem(
|
|
88
|
-
|
|
86
|
+
shop.addItem({
|
|
87
|
+
title: "Сбросить счёт",
|
|
88
|
+
enabled: ({ player }) => player.frags != 0,
|
|
89
89
|
message: "(уже 0)",
|
|
90
|
-
onSelect: (player) => {
|
|
90
|
+
onSelect: ({ player }) => {
|
|
91
91
|
player.frags = 0;
|
|
92
92
|
},
|
|
93
93
|
});
|
|
94
|
-
shop.addItem("Закрыть",
|
|
94
|
+
shop.addItem({ title: "Закрыть", action: "CLOSE_MENU", spaceBefore: 1 });
|
|
95
|
+
|
|
96
|
+
server.addCommand("/shop", ({ player }) => shop.show(player));
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Каждая функция меню — заголовок, `visible`, `enabled`, `onSelect` — получает один объект, контекст меню:
|
|
100
|
+
|
|
101
|
+
- `player` — игрок, которому показано меню: тот, кто его смотрит и выбирает;
|
|
102
|
+
- `target` — игрок, о котором меню: игрок строки в меню-списке, иначе тот, кого передали `show(player, { target })`, а если такого нет — сам `player`;
|
|
103
|
+
- `row` — номер строки в меню-списке, как его дал `listRow()`: сущность, индекс в своём списке — или `id` игрока в списке игроков;
|
|
104
|
+
- `menu` — само меню.
|
|
105
|
+
|
|
106
|
+
Меню-список (его имя начинается с `LIST_`) — строка на каждого игрока, нарисованная по его первому пункту:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const kick = menus.create("LIST_KICK", { title: "Кикнуть игрока" });
|
|
110
|
+
kick.addFilter(({ player, target }) => target.id != player.id, "Кикать некого");
|
|
111
|
+
kick.addItem({
|
|
112
|
+
title: ({ target }) => target.name,
|
|
113
|
+
onSelect: ({ player, target }) => target.kick(`Кикнул ${player.name}`),
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Или строка на каждый элемент своего списка — `menu.setListSource(rows)`:
|
|
95
118
|
|
|
96
|
-
|
|
119
|
+
```ts
|
|
120
|
+
const maps = ["de_dust2", "de_inferno", "de_nuke"];
|
|
121
|
+
|
|
122
|
+
const vote = menus.create("LIST_MAPS", { title: "Следующая карта" });
|
|
123
|
+
vote.setListSource(() => maps.map((map, index) => menus.listRow(index, map)));
|
|
124
|
+
vote.addItem({
|
|
125
|
+
title: ({ row }) => maps[row],
|
|
126
|
+
onSelect: ({ player, row }) => print(0, `${player.name} голосует за ${maps[row]}`),
|
|
127
|
+
});
|
|
97
128
|
```
|
|
98
129
|
|
|
99
|
-
Текст — заголовок, пункт, сообщение — это сам текст или функция, которая даёт его
|
|
130
|
+
Текст — заголовок, пункт, сообщение — это сам текст или функция, которая даёт его по контексту. Обычная строка, если это ключ словаря, переводится для игрока.
|
|
100
131
|
|
|
101
132
|
Пункт говорит, когда он показан и когда его можно выбрать:
|
|
102
133
|
|
|
@@ -115,13 +146,14 @@ server.addCommand("/shop", (player) => shop.show(player));
|
|
|
115
146
|
|
|
116
147
|
| Метод | Что делает |
|
|
117
148
|
| --- | --- |
|
|
118
|
-
| `menu.addItem(
|
|
119
|
-
| `menu.addFixedItem(slot,
|
|
120
|
-
| `menu.addFilter(test, message?)` | Меню-список пропускает строки, на которые `test` отвечает
|
|
121
|
-
| `menu.setListSource(rows)` | Свои строки меню-списка: `listRow(
|
|
149
|
+
| `menu.addItem(item)` | Добавляет пункт: `title` — текст или функция контекста — и `onSelect`, `visible` (пункта нет, пока отвечает «нет»), `enabled` (пункт серый, пока отвечает «нет», — проверка или список `{ when, message }`), `message`, `at`, `spaceBefore`, `spaceAfter`; а для меню, которые называют зарегистрированное плагинами, — `action` и `placeholder`. |
|
|
150
|
+
| `menu.addFixedItem(slot, item)` | Пункт, который на каждой странице занимает слот 1–7. |
|
|
151
|
+
| `menu.addFilter(test, message?)` | Меню-список пропускает строки, на которые `test` отвечает «нет»; `target` — игрок строки. |
|
|
152
|
+
| `menu.setListSource(rows)` | Свои строки меню-списка: `listRow(row, text)`, `textRow(text)`. |
|
|
122
153
|
| `menu.addEventListener("open" \| "close" \| "show", listener)` | События этого меню; `"show"` приходит до открытия, `event.preventDefault()` его отменяет. |
|
|
123
154
|
| `menu.show(player, options?)` | Открывает меню; `false`, если оно не открылось. Опции: `time`, `target`, `resetHistory`, `force`, `skipHistory`. |
|
|
124
155
|
| `menu.refresh()` · `menu.close()` · `menu.clearItems()` | Перерисовать или закрыть у всех, кто его смотрит; убрать пункты. |
|
|
156
|
+
| `menu.runActions(player, line, target?)` | Выполняет строку действий — `"GIVE_HP CLOSE_MENU"` — как её выполняет выбор в меню. |
|
|
125
157
|
| `menu.setTimer(seconds)` · `menu.cancelTimer()` | Общий отсчёт для всех, кто его смотрит. |
|
|
126
158
|
|
|
127
159
|
Его поля — `title`, `time`, `hideBack`, `hideExit`, `locked`, `sharedTimer` — задаются напрямую; `name`, `kind` и `countdown` читаются.
|
|
@@ -131,7 +163,7 @@ server.addCommand("/shop", (player) => shop.show(player));
|
|
|
131
163
|
| `create(name, options?)` | Меню в коде или уже существующее с таким именем. Имя с `LIST_` делает меню-список. Опции: `title` (текст или функция), `time`, `hideBack`, `hideExit`, `locked`, `activeWhen`. |
|
|
132
164
|
| `find(name)` · `register(name)` | Меню по имени; `register` заранее читает его из файла. |
|
|
133
165
|
| `show(player, name, options?)` · `close(player)` · `activeMenu(player)` · `lock(player)` | Меню игрока, какое бы оно ни было. |
|
|
134
|
-
| `addCondition(name, test)` · `addAction(name, handler)` · `addPlaceholder(name, value)` · `addRestriction(name, test, message?)` | То, что называют файлы меню и Pawn-плагины, — ответы функциями; `%name%` в их тексте — плейсхолдер. `message` ограничения пишется рядом с пунктом, который оно гасит, если у пункта или требования нет своего. `menu.addPlaceholder(name, value)` задаёт его одному меню. |
|
|
166
|
+
| `addCondition(name, test)` · `addAction(name, handler)` · `addPlaceholder(name, value)` · `addRestriction(name, test, message?)` | То, что называют файлы меню и Pawn-плагины, — ответы функциями; `%name%` в их тексте — плейсхолдер. Действие, плейсхолдер и ограничение получают контекст меню и имя `name`, по которому их спросили; условие, `(player, viewer, name)`, в меню-списке спрашивается об игроке строки. `message` ограничения пишется рядом с пунктом, который оно гасит, если у пункта или требования нет своего. `menu.addPlaceholder(name, value)` задаёт его одному меню. |
|
|
135
167
|
| `setListSource(name, rows)` · `refresh("A B")` · `conditionChanged(name)` · `addEventListener(type, listener)` | То же для меню по имени и события всех меню. |
|
|
136
168
|
|
|
137
169
|
## Меню в файле
|
|
@@ -252,7 +284,7 @@ VIEW = {
|
|
|
252
284
|
| `labels`: `exit`, `back`, `next`, `number`, `disabled`, `page`, `time` | `[MAIN]` `KEY = { ... }` | Слова кнопок, страницы и отсчёта. |
|
|
253
285
|
| `menus`: `{ NAME: меню }` | `[NAME]` | Меню; имя на `LIST_` — меню-список. |
|
|
254
286
|
| `title` | `TITLE` | Заголовок; он у меню обязателен. |
|
|
255
|
-
| `activeOn` | `ACTIVE_ON` | Условия, при которых меню открывается. |
|
|
287
|
+
| `activeOn` | `ACTIVE_ON` | Условия, только при которых меню открывается. |
|
|
256
288
|
| `hideBack` · `hideExit` | `HIDE_BACK` · `HIDE_EXIT` | `true` (INI: `YES`) убирает кнопку. |
|
|
257
289
|
| `time` · `onTimeout` | `TIME` · `ON_TIMEOUT` | Отсчёт в секундах и действия, когда он закончился. |
|
|
258
290
|
| `locked` · `sharedTimer` | `LOCKED` · `GLOBAL` | Пункты нельзя выбрать; один отсчёт на всех. |
|
|
@@ -333,14 +365,24 @@ INI — формат, который читают и Pawn-плагины, поэ
|
|
|
333
365
|
|
|
334
366
|
### В редакторе
|
|
335
367
|
|
|
336
|
-
Расширение amxts для VS Code проверяет файл меню прямо при наборе — теми же проверками и теми же словами — и подсказывает ключи и имена, которые регистрируют ваши плагины (TypeScript, Pawn и установленные модули), с подсказкой при наведении и переходом к регистрации. Имя, добавленное в плагине, сразу появляется в подсказках файла меню, ещё до сохранения плагина.
|
|
368
|
+
Расширение amxts для VS Code проверяет файл меню прямо при наборе — теми же проверками и теми же словами — и подсказывает ключи и имена, которые регистрируют ваши плагины (TypeScript, Pawn и установленные модули), с подсказкой при наведении и переходом к регистрации. Имя, добавленное в плагине, сразу появляется в подсказках файла меню, ещё до сохранения плагина. Оно ставится из своего `.vsix` — командой `code --install-extension amxts-vscode-<версия>.vsix`.
|
|
337
369
|
|
|
338
370
|
> [!WARNING]
|
|
339
371
|
> Расширение знает только имена, записанные строкой в рабочей области: `menus.addAction(name, ...)` с именем в переменной и имя, которое регистрирует только плагин на Pawn на сервере, — предупреждение в редакторе. В счёт идёт проверка сервера на его первом кадре.
|
|
340
372
|
|
|
341
373
|
## Pawn-плагины
|
|
342
374
|
|
|
343
|
-
Pawn
|
|
375
|
+
Pawn-плагин тоже создаёт, наполняет и показывает меню через Menu Core — 30 нативами, имена которых начинаются с `mc_`: `mc_register_action`, `mc_show_menu`, `mc_add_menu_item` и остальные. Их include лежит в пакете в `include/`:
|
|
376
|
+
|
|
377
|
+
```pawn
|
|
378
|
+
#include <menu_core>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Pawn-плагин, уже собранный с этим include, работает как есть — пересобирать ничего не нужно.
|
|
382
|
+
|
|
383
|
+
Отдавать эти нативы на сервере может только один плагин: если в `plugins.ini` есть другой Pawn-плагин с нативами `mc_*`, закомментируйте его.
|
|
384
|
+
|
|
385
|
+
Menu Core работает на сервере как один из плагинов проекта. Если им не пользуется ни один плагин проекта на TypeScript, оставьте его в сборке для Pawn-плагинов: `pawn: ["@amxts/menu-core"]` в `amxts.config.ts`. Все нативы с сигнатурами — в [PAWN.ru.md](PAWN.ru.md).
|
|
344
386
|
|
|
345
387
|
## Тесты
|
|
346
388
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amxts/menu-core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "Menus for amxts plugins, from ini files or code: conditions, placeholders, lists - and the mc_* natives for Pawn plugins.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "kukson777",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"lint:fix": "oxfmt && oxlint --fix --silent || oxlint --fix --silent || oxlint --fix"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@amxts/config-core": "^0.1.
|
|
53
|
-
"@amxts/core": "^0.
|
|
52
|
+
"@amxts/config-core": "^0.1.1",
|
|
53
|
+
"@amxts/core": "^0.2.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@e18e/eslint-plugin": "^0.8.1",
|
|
Binary file
|