@amxts/core 0.2.3 → 0.3.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.
Files changed (71) hide show
  1. package/README.md +5 -5
  2. package/README.ru.md +5 -5
  3. package/as/amxts.d.ts +13 -3
  4. package/as/entities.ts +571 -501
  5. package/as/events.ts +3323 -557
  6. package/as/facade.ts +3079 -718
  7. package/as/fetch.ts +2 -2
  8. package/as/fs.ts +352 -59
  9. package/as/hlds.ts +121 -120
  10. package/as/hooks.ts +10585 -5177
  11. package/as/lib/check.ts +3 -2
  12. package/as/natives.ts +856 -1272
  13. package/as/promise.ts +37 -27
  14. package/as/vector.ts +94 -14
  15. package/includes/order.txt +17 -19
  16. package/node_modules/assemblyscript/std/assembly/index.d.ts +1 -1
  17. package/package.json +9 -6
  18. package/runtime/deps/assemblyscript/dist/asc.js +136 -42
  19. package/runtime/deps/assemblyscript/dist/assemblyscript.js +141 -141
  20. package/runtime/licenses/README.md +9 -7
  21. package/runtime/licenses/ReGameDLL-LICENSE +21 -0
  22. package/runtime/licenses/ReHLDS-LICENSE +21 -0
  23. package/runtime/licenses/zlib-LICENSE +22 -0
  24. package/runtime/natives.txt +618 -523
  25. package/runtime/patches/README.md +87 -1
  26. package/runtime/patches/assemblyscript-0.28.20-amxts.patch +4838 -416
  27. package/runtime/patches/wamr-2.4.5-amxts.patch +417 -5
  28. package/scripts/auto-imports.ts +1 -1
  29. package/scripts/build-identity.ts +102 -65
  30. package/scripts/build-wasm.ts +17 -13
  31. package/scripts/check.ts +4 -1
  32. package/scripts/client-messages.ts +73 -1
  33. package/scripts/compile-cache.ts +11 -8
  34. package/scripts/compile-pool.ts +6 -3
  35. package/scripts/compile-worker.ts +23 -23
  36. package/scripts/compile.ts +142 -19
  37. package/scripts/docs/as/amxts.ts +10 -4
  38. package/scripts/docs/as/facade.ts +1779 -275
  39. package/scripts/docs/as/fetch.ts +2 -2
  40. package/scripts/docs/as/fs.ts +190 -6
  41. package/scripts/docs/as/vector.ts +81 -0
  42. package/scripts/docs/entities.ts +12 -0
  43. package/scripts/docs/events.ts +49 -13
  44. package/scripts/docs/game.ts +6 -0
  45. package/scripts/docs/messages.ts +10 -1
  46. package/scripts/hlds-events.ts +11 -11
  47. package/scripts/includes.ts +3 -4
  48. package/scripts/leaf-natives.ts +84 -0
  49. package/scripts/player-fields.ts +1 -1
  50. package/scripts/plugin-natives.ts +1 -1
  51. package/scripts/prebuilt.ts +42 -11
  52. package/scripts/project.ts +63 -4
  53. package/scripts/reapi-events.ts +9 -9
  54. package/scripts/server-env.ts +145 -0
  55. package/scripts/shared-modules.ts +23 -7
  56. package/scripts/system.ts +1 -1
  57. package/scripts/tracked-fs.ts +11 -2
  58. package/scripts/typed-commands.ts +153 -15
  59. package/scripts/upgrade-calls.ts +225 -0
  60. package/scripts/upgrade-names.ts +1 -4
  61. package/scripts/upgrade.ts +47 -34
  62. package/src/cli-api.mjs +3 -2
  63. package/src/system.mjs +21 -11
  64. package/src/testing/compile.ts +31 -8
  65. package/src/testing/coroutines.ts +0 -10
  66. package/src/testing/memory.ts +89 -2
  67. package/src/testing/natives.ts +67 -30
  68. package/src/testing/server.ts +1094 -155
  69. package/src/testing/tables.ts +23 -6
  70. package/src/typecheck.mjs +110 -0
  71. package/runtime/licenses/Bun-LICENSE.md +0 -84
@@ -543,7 +543,7 @@ export default {
543
543
 
544
544
  const { data, error } = await useFetch<Weather>("https://example.com/weather", { query: { city: "Paris" } });
545
545
  if (error) return console.error(error.message);
546
- print(player, \`\${data!.temperature} °C\`);
546
+ player.print(\`\${data!.temperature} °C\`);
547
547
  \`\`\`
548
548
 
549
549
  A body that is an object goes as JSON: \`useFetch<Answer, Report>(url, { method: "POST", body: report })\`.
@@ -559,7 +559,7 @@ export default {
559
559
 
560
560
  const { data, error } = await useFetch<Weather>("https://example.com/weather", { query: { city: "Paris" } });
561
561
  if (error) return console.error(error.message);
562
- print(player, \`\${data!.temperature} °C\`);
562
+ player.print(\`\${data!.temperature} °C\`);
563
563
  \`\`\`
564
564
 
565
565
  Тело-объект уходит как JSON: \`useFetch<Answer, Report>(url, { method: "POST", body: report })\`.
@@ -31,21 +31,25 @@ export default {
31
31
  },
32
32
  'writeFileSync': {
33
33
  en: `
34
- Writes text to a file, replacing what was there; \`false\` when it cannot be
35
- opened (a folder that does not exist, for one).
34
+ Writes text, or bytes - a \`Uint8Array\` or an \`ArrayBuffer\` - to a file,
35
+ replacing what was there; \`false\` when it cannot be opened (a folder that
36
+ does not exist, for one). Bytes are not written past the game folder.
36
37
 
37
38
  \`\`\`ts
38
39
  fs.writeFileSync("addons/amxmodx/data/last-map.txt", server.map);
40
+ fs.writeFileSync("maps/kz_map.bsp", await response.arrayBuffer());
39
41
  \`\`\`
40
42
 
41
43
  Pawn: \`fopen\`, \`fputs\`
42
44
  `,
43
45
  ru: `
44
- Записывает текст в файл вместо того, что там было; \`false\`, если файл не
45
- удалось открыть (например, такой папки нет).
46
+ Записывает текст или байты — \`Uint8Array\` или \`ArrayBuffer\` — в файл
47
+ вместо того, что там было; \`false\`, если файл не удалось открыть (например,
48
+ такой папки нет). Байты не пишутся за пределы папки игры.
46
49
 
47
50
  \`\`\`ts
48
51
  fs.writeFileSync("addons/amxmodx/data/last-map.txt", server.map);
52
+ fs.writeFileSync("maps/kz_map.bsp", await response.arrayBuffer());
49
53
  \`\`\`
50
54
 
51
55
  Pawn: \`fopen\`, \`fputs\`
@@ -53,16 +57,70 @@ export default {
53
57
  },
54
58
  'appendFileSync': {
55
59
  en: `
56
- Adds text to the end of a file, making it if there is none; \`false\` when it cannot be opened.
60
+ Adds text or bytes to the end of a file, making it if there is none; \`false\` when it cannot be opened.
57
61
 
58
62
  Pawn: \`fopen(path, "a")\`, \`fputs\`
59
63
  `,
60
64
  ru: `
61
- Дописывает текст в конец файла, создавая его, если файла нет; \`false\`, если его не удалось открыть.
65
+ Дописывает текст или байты в конец файла, создавая его, если файла нет; \`false\`, если его не удалось открыть.
62
66
 
63
67
  Pawn: \`fopen(path, "a")\`, \`fputs\`
64
68
  `,
65
69
  },
70
+ 'readBytesSync': {
71
+ en: `
72
+ Reads a whole file as bytes; \`null\` when it cannot be opened or the path
73
+ leaves the game folder.
74
+ `,
75
+ ru: `
76
+ Читает весь файл как байты; \`null\`, если его не удалось открыть или путь
77
+ ведёт за пределы папки игры.
78
+ `,
79
+ },
80
+ 'readBytes': {
81
+ en: `\`readBytesSync\` as a promise, rejected when the file cannot be opened.`,
82
+ ru: `\`readBytesSync\` в виде промиса; отклоняется, если файл не удалось открыть.`,
83
+ },
84
+ 'ArchiveEntry': {
85
+ en: `A file of an archive, from \`extract\`: its path inside the archive and its bytes.`,
86
+ ru: `Файл архива из \`extract\`: его путь внутри архива и его байты.`,
87
+ },
88
+ 'ArchiveEntry.path': {
89
+ en: `The path inside the archive, \`/\` between folders: \`"maps/de_dust2.bsp"\`.`,
90
+ ru: `Путь внутри архива, \`/\` между папками: \`"maps/de_dust2.bsp"\`.`,
91
+ },
92
+ 'ArchiveEntry.data': {
93
+ en: `The file's bytes.`,
94
+ ru: `Байты файла.`,
95
+ },
96
+ 'extract': {
97
+ en: `
98
+ The files of an archive, with their paths inside it; folders are not
99
+ listed. The archive's kind is read off its first bytes: a zip archive,
100
+ stored or deflated. Throws an \`Error\` for a kind it does not read - naming
101
+ the ones it does - an entry whose bytes do not check out, and a path that
102
+ would leave the folder it is unpacked into.
103
+
104
+ \`\`\`ts
105
+ for (const entry of fs.extract(await response.arrayBuffer())) {
106
+ fs.writeFileSync(entry.path, entry.data);
107
+ }
108
+ \`\`\`
109
+ `,
110
+ ru: `
111
+ Файлы архива с их путями внутри него; папки не перечисляются. Вид архива
112
+ читается по его первым байтам: zip-архив, сохранённый как есть или сжатый
113
+ deflate. Бросает \`Error\` для вида, который не читает, — называя те, что
114
+ читает, — для файла, чьи байты не сходятся, и для пути, который ведёт за
115
+ пределы папки, куда архив распаковывается.
116
+
117
+ \`\`\`ts
118
+ for (const entry of fs.extract(await response.arrayBuffer())) {
119
+ fs.writeFileSync(entry.path, entry.data);
120
+ }
121
+ \`\`\`
122
+ `,
123
+ },
66
124
  'existsSync': {
67
125
  en: `
68
126
  \`true\` when the file or folder exists.
@@ -170,4 +228,130 @@ export default {
170
228
  en: `\`readdirSync\` as a promise, rejected when there is no such folder.`,
171
229
  ru: `\`readdirSync\` в виде промиса; отклоняется, если такой папки нет.`,
172
230
  },
231
+ 'unlinkSync': {
232
+ en: `
233
+ Deletes a file; \`false\` when there is none or it cannot be deleted.
234
+
235
+ Pawn: \`delete_file\`, \`unlink\`
236
+ `,
237
+ ru: `
238
+ Удаляет файл; \`false\`, если его нет или его не удалось удалить.
239
+
240
+ Pawn: \`delete_file\`, \`unlink\`
241
+ `,
242
+ },
243
+ 'renameSync': {
244
+ en: `
245
+ Moves or renames a file: \`fs.renameSync("addons/amxmodx/data/top.txt",
246
+ "addons/amxmodx/data/top.old")\`; \`false\` when it cannot.
247
+
248
+ Pawn: \`rename_file\`
249
+ `,
250
+ ru: `
251
+ Перемещает или переименовывает файл: \`fs.renameSync("addons/amxmodx/data/top.txt",
252
+ "addons/amxmodx/data/top.old")\`; \`false\`, если не удалось.
253
+
254
+ Pawn: \`rename_file\`
255
+ `,
256
+ },
257
+ 'rmdirSync': {
258
+ en: `
259
+ Deletes an empty folder; \`false\` when there is none, it holds something or
260
+ it cannot be deleted.
261
+
262
+ Pawn: \`rmdir\`
263
+ `,
264
+ ru: `
265
+ Удаляет пустую папку; \`false\`, если её нет, в ней что-то есть или её не
266
+ удалось удалить.
267
+
268
+ Pawn: \`rmdir\`
269
+ `,
270
+ },
271
+ 'Stats': {
272
+ en: `
273
+ A file's or a folder's facts from \`statSync\`, as Node's \`fs.Stats\`.
274
+ `,
275
+ ru: `
276
+ Сведения о файле или папке из \`statSync\`, как \`fs.Stats\` в Node.
277
+ `,
278
+ },
279
+ 'Stats.size': {
280
+ en: `
281
+ The size in bytes; \`0\` for a folder.
282
+ `,
283
+ ru: `
284
+ Размер в байтах; \`0\` для папки.
285
+ `,
286
+ },
287
+ 'Stats.mtime': {
288
+ en: `
289
+ The moment it last changed.
290
+ `,
291
+ ru: `
292
+ Момент его последнего изменения.
293
+ `,
294
+ },
295
+ 'Stats.isFile': {
296
+ en: `
297
+ Whether it is a file.
298
+ `,
299
+ ru: `
300
+ Файл ли это.
301
+ `,
302
+ },
303
+ 'Stats.isDirectory': {
304
+ en: `
305
+ Whether it is a folder.
306
+ `,
307
+ ru: `
308
+ Папка ли это.
309
+ `,
310
+ },
311
+ 'statSync': {
312
+ en: `
313
+ A file's or a folder's size and the moment it last changed; \`null\` when
314
+ there is none: \`fs.statSync("addons/amxmodx/logs/error.log")?.size\`.
315
+
316
+ Pawn: \`file_size\`, \`GetFileTime\`
317
+ `,
318
+ ru: `
319
+ Размер файла или папки и момент последнего изменения; \`null\`, если их нет:
320
+ \`fs.statSync("addons/amxmodx/logs/error.log")?.size\`.
321
+
322
+ Pawn: \`file_size\`, \`GetFileTime\`
323
+ `,
324
+ },
325
+ 'unlink': {
326
+ en: `
327
+ \`unlinkSync\` as a promise, rejected when the file cannot be deleted.
328
+ `,
329
+ ru: `
330
+ \`unlinkSync\` в виде промиса; отклоняется, если файл не удалось удалить.
331
+ `,
332
+ },
333
+ 'rename': {
334
+ en: `
335
+ \`renameSync\` as a promise, rejected when the file cannot be moved.
336
+ `,
337
+ ru: `
338
+ \`renameSync\` в виде промиса; отклоняется, если файл не удалось переместить.
339
+ `,
340
+ },
341
+ 'rmdir': {
342
+ en: `
343
+ \`rmdirSync\` as a promise, rejected when the folder cannot be deleted.
344
+ `,
345
+ ru: `
346
+ \`rmdirSync\` в виде промиса; отклоняется, если папку не удалось удалить.
347
+ `,
348
+ },
349
+ 'stat': {
350
+ en: `
351
+ \`statSync\` as a promise, rejected when there is no such file or folder.
352
+ `,
353
+ ru: `
354
+ \`statSync\` в виде промиса; отклоняется, если такого файла или папки нет.
355
+ `,
356
+ },
173
357
  };
@@ -45,4 +45,85 @@ export default {
45
45
  en: `A new vector in the same direction, one unit long; a zero vector stays zero.`,
46
46
  ru: `Новый вектор того же направления, длиной в единицу; нулевой вектор так и остаётся нулевым.`,
47
47
  },
48
+ 'Vector.fromAngles': {
49
+ en: `
50
+ The direction angles look in, one unit long:
51
+ \`Vector.fromAngles(player.viewAngle).scale(500)\` is a push the way the
52
+ player looks.
53
+
54
+ Pawn: \`angle_vector(..., ANGLEVECTOR_FORWARD, ...)\`, \`velocity_by_aim\`
55
+ `,
56
+ ru: `
57
+ Направление, куда смотрят углы, длиной в единицу:
58
+ \`Vector.fromAngles(player.viewAngle).scale(500)\` — толчок туда, куда смотрит
59
+ игрок.
60
+
61
+ Pawn: \`angle_vector(..., ANGLEVECTOR_FORWARD, ...)\`, \`velocity_by_aim\`
62
+ `,
63
+ },
64
+ 'Vector.directions': {
65
+ en: `
66
+ The three directions of angles, one unit long each: where they look,
67
+ to their right and above them - \`const { forward, right } =
68
+ Vector.directions(player.viewAngle)\`.
69
+
70
+ Pawn: \`angle_vector\`, \`engfunc(EngFunc_MakeVectors, ...)\`
71
+ `,
72
+ ru: `
73
+ Три направления углов, каждое длиной в единицу: куда они смотрят, вправо от
74
+ них и вверх — \`const { forward, right } = Vector.directions(player.viewAngle)\`.
75
+
76
+ Pawn: \`angle_vector\`, \`engfunc(EngFunc_MakeVectors, ...)\`
77
+ `,
78
+ },
79
+ 'Vector.toAngles': {
80
+ en: `
81
+ The angles that look along this vector, as a player's view holds them -
82
+ a pitch below the horizon positive: \`player.viewAngle =
83
+ target.subtract(player.eyes).toAngles()\`. \`Vector.fromAngles\` turns
84
+ them back.
85
+
86
+ Pawn: \`vector_to_angle\`, \`engfunc(EngFunc_VecToAngles, ...)\`
87
+ `,
88
+ ru: `
89
+ Углы, которые смотрят вдоль этого вектора, как их держит взгляд игрока, —
90
+ наклон ниже горизонта положительный: \`player.viewAngle =
91
+ target.subtract(player.eyes).toAngles()\`. \`Vector.fromAngles\` превращает их
92
+ обратно.
93
+
94
+ Pawn: \`vector_to_angle\`, \`engfunc(EngFunc_VecToAngles, ...)\`
95
+ `,
96
+ },
97
+ 'Directions': {
98
+ en: `
99
+ The three directions of angles: ahead, to the right and up - \`Vector.directions\`.
100
+ `,
101
+ ru: `
102
+ Три направления углов: вперёд, вправо и вверх — \`Vector.directions\`.
103
+ `,
104
+ },
105
+ 'Directions.forward': {
106
+ en: `
107
+ The direction they look in.
108
+ `,
109
+ ru: `
110
+ Направление, куда они смотрят.
111
+ `,
112
+ },
113
+ 'Directions.right': {
114
+ en: `
115
+ To their right.
116
+ `,
117
+ ru: `
118
+ Вправо от них.
119
+ `,
120
+ },
121
+ 'Directions.up': {
122
+ en: `
123
+ Above them.
124
+ `,
125
+ ru: `
126
+ Вверх от них.
127
+ `,
128
+ },
48
129
  };
@@ -737,6 +737,10 @@ export const ENTITY_FIELDS: Record<string, Text> = {
737
737
  en: `The player's field of view in degrees: \`90\` is normal, \`40\` and \`10\` through a sniper scope. Setting it widens or narrows his view - \`110\` shows more - until the game sets it again: at spawn, when he draws a weapon, when he zooms.`,
738
738
  ru: `Поле зрения игрока в градусах: \`90\` — обычное, \`40\` и \`10\` — в снайперский прицел. Запись расширяет или сужает обзор — \`110\` показывает больше, — пока игра не поставит своё: при появлении, когда он достаёт оружие, когда приближает прицел.`,
739
739
  },
740
+ m_flFallVelocity: {
741
+ en: `The player's falling speed, units per second, positive downwards: the one the game works fall damage out from on landing. Setting it sets the entity's \`fallVelocity\` too - \`player.fallVelocity = 0\` lands him without damage.`,
742
+ ru: `Скорость падения игрока, единиц в секунду, вниз положительная: по ней игра считает урон при приземлении. Запись ставит и \`fallVelocity\` сущности — \`player.fallVelocity = 0\` приземляет без урона.`,
743
+ },
740
744
  m_iLastZoom: {
741
745
  en: `The player's zoom (field of view) to go back to after a sniper rifle reloads or fires.`,
742
746
  ru: `Зум игрока (поле зрения), к которому вернуться после перезарядки или выстрела снайперской винтовки.`,
@@ -1983,6 +1987,14 @@ Pawn: \`is_valid_ent\`, \`is_entity\``,
1983
1987
  ru: `\`true\`, пока сущность есть в мире: \`false\`, когда движок её освободил (\`remove()\` освобождает её в конце кадра), для ушедшего игрока и для \`0\` — «нет сущности». Номер из события или хука может указывать на сущность, которой уже нет.
1984
1988
 
1985
1989
  Pawn: \`is_valid_ent\`, \`is_entity\``,
1990
+ },
1991
+ 'Entity.dropToFloor': {
1992
+ en: `Drops the entity straight down onto what is under it, as the game puts an item on the floor when a map starts: \`true\` when it landed, \`false\` when there is nothing under it within 256 units or it is stuck in something.
1993
+
1994
+ Pawn: \`drop_to_floor\`, \`engfunc(EngFunc_DropToFloor, ...)\``,
1995
+ ru: `Опускает сущность вертикально вниз на то, что под ней, как игра кладёт предмет на пол при старте карты: \`true\`, если она опустилась, \`false\`, если под ней ничего нет в пределах 256 единиц или она во что-то застряла.
1996
+
1997
+ Pawn: \`drop_to_floor\`, \`engfunc(EngFunc_DropToFloor, ...)\``,
1986
1998
  },
1987
1999
  'Entity.emitSound': {
1988
2000
  en: `Plays a sound from the entity, heard by everyone near and fading with distance: \`player.emitSound("myplugin/hit.wav")\`. The path is under \`sound/\`, as \`server.precache\` takes it; \`options\` set the channel, the volume, the attenuation and the pitch.
@@ -1,6 +1,6 @@
1
1
  // Our own words for the server events, in both languages the project speaks.
2
2
  //
3
- // scripts/generate-host.ts writes these into as/events.ts as the tooltips an
3
+ // scripts/generate-image.ts writes these into as/events.ts as the tooltips an
4
4
  // editor shows on `server.addEventListener("...")`, on the event class and on
5
5
  // each of its fields. The language is AMXTS_DOCS_LANG in .env ("en" by
6
6
  // default, or "ru"). An event missing here, or missing a language, falls back
@@ -59,16 +59,10 @@ export const EVENTS: Record<string, EventDoc> = {
59
59
  },
60
60
  plugin_end: {
61
61
  summary: {
62
- en: `The map is ending or the server is shutting down: save what has to survive.`,
63
- ru: `Карта заканчивается или сервер выключается: сохраните то, что должно пережить смену карты.`,
62
+ en: `The plugin stops - the map ends, the server shuts down, or \`amxts_reload\`/\`amxts_unload\` stops it: save what has to survive.`,
63
+ ru: `Плагин останавливается — карта заканчивается, сервер выключается или его останавливает \`amxts_reload\`/\`amxts_unload\`: сохраните то, что должно это пережить.`,
64
64
  },
65
65
  },
66
- plugin_pause: {
67
- summary: { en: `An admin paused this plugin.`, ru: `Админ поставил плагин на паузу.` },
68
- },
69
- plugin_unpause: {
70
- summary: { en: `An admin resumed this plugin.`, ru: `Админ снял плагин с паузы.` },
71
- },
72
66
  server_changelevel: {
73
67
  summary: {
74
68
  en: `The server is about to change the map.`,
@@ -78,6 +72,23 @@ export const EVENTS: Record<string, EventDoc> = {
78
72
  map: { en: `The map the server changes to, e.g. \`"de_dust2"\`.`, ru: `Карта, на которую переходит сервер, например \`"de_dust2"\`.` },
79
73
  },
80
74
  },
75
+ plugin_log: {
76
+ summary: {
77
+ en: `A line the game writes to its log - a round's end, a kill, a bomb planted: \`event.text\` whole, \`event.args\` its parts.`,
78
+ ru: `Строка, которую игра пишет в свой лог, — конец раунда, убийство, заложенная бомба: \`event.text\` целиком, \`event.args\` по частям.`,
79
+ },
80
+ fields: {
81
+ text: {
82
+ en: `The line as the game logs it, without the date: \`World triggered "Round_Start"\`.`,
83
+ ru: `Строка так, как её пишет игра, без даты: \`World triggered "Round_Start"\`.`,
84
+ },
85
+ args: {
86
+ en: `The line in parts, as AMX Mod X splits it: a quoted text, a text in parentheses and the words between, each one part - \`World triggered "Round_Draw"\` is \`["World triggered", "Round_Draw"]\`.`,
87
+ ru: `Строка по частям, как её делит AMX Mod X: текст в кавычках, текст в скобках и слова между ними — каждое отдельной частью; \`World triggered "Round_Draw"\` — это \`["World triggered", "Round_Draw"]\`.`,
88
+ },
89
+ },
90
+ example: `server.addEventListener("log", (event) => {\n\tif (event.text.startsWith("World triggered")) console.log(event.args[1]);\n});`,
91
+ },
81
92
  client_connect: {
82
93
  summary: {
83
94
  en: `A player started connecting. The player is not in the game yet: show him anything after \`"putInServer"\`.`,
@@ -117,7 +128,7 @@ export const EVENTS: Record<string, EventDoc> = {
117
128
  ru: `Игрок зашёл и уже в игре: момент поприветствовать его.`,
118
129
  },
119
130
  fields: { player },
120
- example: `server.addEventListener("putInServer", (event) => {\n\tprint(event.player, "Welcome!");\n});`,
131
+ example: `server.addEventListener("putInServer", (event) => {\n\tevent.player.print("Welcome!");\n});`,
121
132
  },
122
133
  client_disconnected: {
123
134
  summary: {
@@ -154,10 +165,30 @@ export const EVENTS: Record<string, EventDoc> = {
154
165
  },
155
166
  client_command: {
156
167
  summary: {
157
- en: `A player sent a console command. For one command, \`server.addCommand("name", handler)\` is simpler.`,
158
- ru: `Игрок отправил консольную команду. Для одной команды проще \`server.addCommand("name", handler)\`.`,
168
+ en: `A player sent a console command - a chat line is \`say\` or \`say_team\`. For one command, \`server.addCommand("name", handler)\` is simpler.`,
169
+ ru: `Игрок отправил консольную команду — строка в чат это \`say\` или \`say_team\`. Для одной команды проще \`server.addCommand("name", handler)\`.`,
159
170
  },
160
- fields: { player },
171
+ notes: [],
172
+ fields: {
173
+ player,
174
+ command: {
175
+ en: `The command's name, as typed, e.g. \`"say"\`, \`"say_team"\`, \`"buy"\`.`,
176
+ ru: `Имя команды, как её ввели, например \`"say"\`, \`"say_team"\`, \`"buy"\`.`,
177
+ },
178
+ args: {
179
+ en: `The command's words after its name; for \`say\` and \`say_team\`, the words of the chat line - \`say /cp now\` is \`["/cp", "now"]\`.`,
180
+ ru: `Слова команды после имени; у \`say\` и \`say_team\` — слова строки чата: \`say /cp now\` — это \`["/cp", "now"]\`.`,
181
+ },
182
+ text: {
183
+ en: `The command's text after its name, as one line; for \`say\` and \`say_team\`, the chat line without its quotes.`,
184
+ ru: `Текст команды после имени одной строкой; у \`say\` и \`say_team\` — строка чата без кавычек.`,
185
+ },
186
+ preventDefault: {
187
+ en: `Stops the command: the game does not run it - a chat line is not said - and no plugin's \`server.addCommand\` hears it. The other listeners of \`"command"\` still do.`,
188
+ ru: `Останавливает команду: игра её не выполняет — строка чата не уходит в чат, — и её не слышит ни один \`server.addCommand\` плагинов. Остальные слушатели \`"command"\` её слышат.`,
189
+ },
190
+ },
191
+ example: `server.addEventListener("command", (event) => {\n\tif (event.command == "say" && event.args[0] == "/cp") checkpoint(event.player);\n});`,
161
192
  },
162
193
  client_infochanged: {
163
194
  summary: {
@@ -221,6 +252,11 @@ export const EVENTS: Record<string, EventDoc> = {
221
252
  iparam2: { en: `The event's second whole-number parameter.`, ru: `Второй целый параметр события.` },
222
253
  bparam1: { en: `The event's first flag parameter, \`1\` or \`0\`.`, ru: `Первый параметр-флаг события, \`1\` или \`0\`.` },
223
254
  bparam2: { en: `The event's second flag parameter, \`1\` or \`0\`.`, ru: `Второй параметр-флаг события, \`1\` или \`0\`.` },
255
+ preventDefault: { en: `Stops the event: no player gets it - a shot's sound and flash with it.`, ru: `Останавливает событие: его не получает ни один игрок — вместе со звуком и вспышкой выстрела.` },
256
+ recipients: {
257
+ en: `Plays the event only to these of the players it would reach - \`event.recipients = players.filter(...)\`; the others do not hear the shot. The player who caused it is not left out by it.`,
258
+ ru: `Проигрывает событие только этим из игроков, до которых оно дошло бы, — \`event.recipients = players.filter(...)\`; остальные не слышат выстрела. Игрока, из-за которого оно случилось, это не исключает.`,
259
+ },
224
260
  },
225
261
  },
226
262
  };
@@ -30,6 +30,12 @@ const theWeapon: Text = { en: `The weapon the event is about - one of the class
30
30
  const theEntity: Text = { en: `The entity the event is about - one of the class \`classname\` names.`, ru: `Сущность, о которой событие, — одного класса, названного в \`classname\`.` };
31
31
 
32
32
  export const GAME: Record<string, GameDoc> = {
33
+ entityState: {
34
+ summary: {
35
+ en: `The state of an entity a player can see, as he is sent it each frame: its place, angles, look - written for him alone - or hidden from him with \`event.preventDefault()\`. \`{ classname }\` names the entities a listener is about; the module passes only those.`,
36
+ ru: `Состояние сущности, которую видит игрок, как он получает его каждый кадр: её место, углы, вид — изменённые только для него, — или скрытая от него через \`event.preventDefault()\`. \`{ classname }\` называет сущности, о которых слушатель; модуль передаёт только их.`,
37
+ },
38
+ },
33
39
  touch: {
34
40
  summary: {
35
41
  en: `An entity touched another: \`event.toucher\` moved into \`event.touched\`. Pass the classes it is about as the third argument - \`{ toucher: "player", touched: "player" }\` - so only those touches reach the plugin; \`event.preventDefault()\` blocks the touch.`,
@@ -1,6 +1,6 @@
1
1
  // Our own words for the messages the server sends its clients, as
2
2
  // server.addMessageListener(name, ...) hears them - in both languages.
3
- // scripts/generate-host.ts writes them into as/events.ts as the tooltips an
3
+ // scripts/generate-image.ts writes them into as/events.ts as the tooltips an
4
4
  // editor shows on the name, the event and its fields: the summary, then the
5
5
  // game's name of the message, so a Pawn author who searches for `DeathMsg`
6
6
  // finds `death`. A name that hears a few of the game's messages has its words
@@ -427,6 +427,7 @@ export const MESSAGES: Record<string, EventDoc> = {
427
427
  slot: { en: `The slot the weapon is in, from \`0\`.`, ru: `Слот оружия, с \`0\`.` },
428
428
  position: { en: `The weapon's place in the slot, from \`0\`.`, ru: `Место оружия в слоте, с \`0\`.` },
429
429
  weapon: { en: `The weapon, by its kind, e.g. \`"ak47"\`.`, ru: `Оружие по его виду, например \`"ak47"\`.` },
430
+ flags: { en: `The weapon's ITEM_FLAG_* bits: \`8\` for one thrown when it is empty, \`16\` for one used only once.`, ru: `Биты ITEM_FLAG_* оружия: \`8\` — выбрасывается, когда пусто, \`16\` — используется один раз.` },
430
431
  },
431
432
  },
432
433
  WeapPickup: {
@@ -460,3 +461,11 @@ export const ANY_MESSAGE: { args: Text; player: Text } = {
460
461
  args: { en: `Its arguments are read by place, through \`event.args\`.`, ru: `Его аргументы читаются по месту, через \`event.args\`.` },
461
462
  player: receiver,
462
463
  };
464
+
465
+ /** The words of a message's fields for sending: what player.send and server.send take for a name. */
466
+ export function sendFields(name: string, message: string): Text {
467
+ return {
468
+ en: `What \`player.send("${name}", ...)\` and \`server.send("${name}", ...)\` take: the game's \`${message}\` message, each field one of its arguments. A field left out goes as \`0\` or empty text.\n\nPawn: \`message_begin(..., get_user_msgid("${message}"))\`, \`write_*\`, \`message_end\``,
469
+ ru: `Что принимают \`player.send("${name}", ...)\` и \`server.send("${name}", ...)\`: сообщение игры \`${message}\`, каждое поле — один его аргумент. Пропущенное поле уходит как \`0\` или пустой текст.\n\nPawn: \`message_begin(..., get_user_msgid("${message}"))\`, \`write_*\`, \`message_end\``,
470
+ };
471
+ }
@@ -43,8 +43,8 @@ function noAnswer(en: string, ru: string): Text {
43
43
 
44
44
  function buy(en: string, ru: string): Text {
45
45
  return noAnswer(
46
- `heard through cstrike's \`CS_OnBuy\`: \`preventDefault()\` stops the purchase; ${en}`,
47
- `слышно через \`CS_OnBuy\` модуля cstrike: \`preventDefault()\` отменяет покупку; ${ru}`,
46
+ `heard as the game checks the purchase, for an item a buy command or a buy menu names: \`preventDefault()\` stops it; ${en}`,
47
+ `слышно, когда игра проверяет покупку, для предмета, который называет команда покупки или меню покупки: \`preventDefault()\` отменяет её; ${ru}`,
48
48
  );
49
49
  }
50
50
 
@@ -79,7 +79,7 @@ export const HEARD: Record<string, HeardEvent> = {
79
79
  },
80
80
  gameThink: { class: 'B', backend: 'server_frame', gaps: after },
81
81
  mapReset: { class: 'B', backend: 'the decal_reset.sc playback CleanUpMap ends with', gaps: after },
82
- intermission: { class: 'B', backend: 'register_event "30" (SVC_INTERMISSION)', gaps: after },
82
+ intermission: { class: 'B', backend: 'the engine message 30 (SVC_INTERMISSION)', gaps: after },
83
83
  changeLevel: { class: 'B', backend: 'server_changelevel', gaps: { en: '`preventDefault()` does nothing', ru: '`preventDefault()` ничего не делает' } },
84
84
 
85
85
  // Spawning and dying.
@@ -138,15 +138,15 @@ export const HEARD: Record<string, HeardEvent> = {
138
138
  '`amount` — на сколько сдвинулись его деньги с тех пор, как игра в последний раз их ему прислала, а `reason` читается как `"none"`',
139
139
  ),
140
140
  },
141
- buyWeapon: { class: 'B', backend: 'cstrike CS_OnBuy, a weapon', gaps: buy('`event.result` reads as `null`', '`event.result` читается как `null`') },
142
- buyItem: { class: 'B', backend: 'cstrike CS_OnBuy, the equipment', gaps: buy('heard for the equipment menu\'s items', 'слышно для предметов меню снаряжения') },
143
- buyAmmo: { class: 'B', backend: 'cstrike CS_OnBuy, ammo', gaps: buy('`weapon` reads as the world, `blinkMoney` as `true`', '`weapon` читается как мир, `blinkMoney` — как `true`') },
141
+ buyWeapon: { class: 'B', backend: 'the game\'s CanBuyThis, a weapon (CS_OnBuy\'s place)', gaps: buy('`event.result` reads as `null`', '`event.result` читается как `null`') },
142
+ buyItem: { class: 'B', backend: 'the game\'s CanPlayerBuy and GiveNamedItem, the equipment (CS_OnBuy\'s place)', gaps: buy('heard for the equipment menu\'s items', 'слышно для предметов меню снаряжения') },
143
+ buyAmmo: { class: 'B', backend: 'the game\'s GiveNamedItem, ammo (CS_OnBuy\'s place)', gaps: buy('`weapon` reads as the world, `blinkMoney` as `true`', '`weapon` читается как мир, `blinkMoney` — как `true`') },
144
144
  itemRestricted: {
145
145
  class: 'B',
146
- backend: 'cstrike CS_OnBuyAttempt',
146
+ backend: 'the game\'s ClientCommand, a buy command or a buy menu\'s number (CS_OnBuyAttempt\'s place)',
147
147
  gaps: {
148
- en: 'asked only for `"buying"`, through cstrike\'s `CS_OnBuyAttempt`: answering `true` forbids the purchase, `false` lets the game go on',
149
- ru: 'спрашивается только про `"buying"`, через `CS_OnBuyAttempt` модуля cstrike: ответ `true` запрещает покупку, `false` оставляет решение игре',
148
+ en: 'asked only for `"buying"`, as a player asks to buy: answering `true` forbids the purchase, `false` lets the game go on',
149
+ ru: 'спрашивается только про `"buying"`, когда игрок просит покупку: ответ `true` запрещает покупку, `false` оставляет решение игре',
150
150
  },
151
151
  },
152
152
 
@@ -234,7 +234,7 @@ export const HEARD: Record<string, HeardEvent> = {
234
234
  },
235
235
  },
236
236
 
237
- // Precaching: fakemeta's forwards, added at plugin_precache, before the game's own.
237
+ // Precaching: the engine's functions, hooked as the plugin loads, before the game's own precaches.
238
238
  precacheModel: { class: 'B', backend: 'FM_PrecacheModel', gaps: precached },
239
239
  precacheSound: { class: 'B', backend: 'FM_PrecacheSound', gaps: precached },
240
240
  precacheFile: { class: 'B', backend: 'FM_PrecacheGeneric', gaps: precached },
@@ -341,7 +341,7 @@ export interface HeardField {
341
341
  }
342
342
 
343
343
  export const HEARD_FIELDS: Record<string, HeardField> = {
344
- gameName: { class: 'A', backend: 'fakemeta: GetGameDescription read, and answered with the text written' },
344
+ gameName: { class: 'A', backend: 'the game\'s GetGameDescription read, and answered with the text written' },
345
345
  timeLimit: {
346
346
  class: 'B',
347
347
  backend: 'mp_timelimit and AMX Mod X\'s time left',
@@ -14,9 +14,8 @@
14
14
  * includes/ and AMX Mod X's. The core's includes/vendor/ is the core's own:
15
15
  * a project sees what its server has.
16
16
  *
17
- * Everything available is parsed for its natives, but the host plugin pulls only
18
- * what order.txt lists: referencing a native of a module the server does not
19
- * load makes AMX Mod X refuse the plugin, and the runtime goes down with it.
17
+ * Everything available is parsed for its natives, but the natives' image
18
+ * pulls only what order.txt lists.
20
19
  */
21
20
  import { realpathSync } from 'node:fs';
22
21
  import { join, resolve } from 'node:path';
@@ -91,7 +90,7 @@ export function readInclude(name: string): string {
91
90
  }
92
91
 
93
92
  /**
94
- * Parses includes/order.txt. A bare name is included and (by the host
93
+ * Parses includes/order.txt. A bare name is included and (by the image's
95
94
  * generator) pulled; a line starting with `-` is a deny marker - the name
96
95
  * still has to resolve (an include that #includes it needs it on disk), but
97
96
  * it is excluded from whatever build the caller pulls into a native table.