@amxts/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.
Files changed (247) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/README.ru.md +162 -0
  4. package/amxmodx/base/include/amxconst.inc +594 -0
  5. package/amxmodx/base/include/amxmisc.inc +902 -0
  6. package/amxmodx/base/include/amxmodx.inc +3447 -0
  7. package/amxmodx/base/include/amxmodx_version.inc +20 -0
  8. package/amxmodx/base/include/cellarray.inc +525 -0
  9. package/amxmodx/base/include/cellstack.inc +166 -0
  10. package/amxmodx/base/include/celltrie.inc +412 -0
  11. package/amxmodx/base/include/core.inc +202 -0
  12. package/amxmodx/base/include/csstats.inc +298 -0
  13. package/amxmodx/base/include/csstats_const.inc +29 -0
  14. package/amxmodx/base/include/cstrike.inc +1287 -0
  15. package/amxmodx/base/include/cstrike_const.inc +521 -0
  16. package/amxmodx/base/include/csx.inc +259 -0
  17. package/amxmodx/base/include/cvars.inc +547 -0
  18. package/amxmodx/base/include/datapack.inc +161 -0
  19. package/amxmodx/base/include/dbi.inc +154 -0
  20. package/amxmodx/base/include/dodconst.inc +152 -0
  21. package/amxmodx/base/include/dodfun.inc +152 -0
  22. package/amxmodx/base/include/dodstats.inc +69 -0
  23. package/amxmodx/base/include/dodx.inc +161 -0
  24. package/amxmodx/base/include/engine.inc +1343 -0
  25. package/amxmodx/base/include/engine_const.inc +310 -0
  26. package/amxmodx/base/include/engine_stocks.inc +248 -0
  27. package/amxmodx/base/include/esf.inc +70 -0
  28. package/amxmodx/base/include/esf_const.inc +74 -0
  29. package/amxmodx/base/include/fakemeta.inc +1127 -0
  30. package/amxmodx/base/include/fakemeta_const.inc +793 -0
  31. package/amxmodx/base/include/fakemeta_stocks.inc +395 -0
  32. package/amxmodx/base/include/fakemeta_util.inc +882 -0
  33. package/amxmodx/base/include/file.inc +666 -0
  34. package/amxmodx/base/include/float.inc +422 -0
  35. package/amxmodx/base/include/fun.inc +333 -0
  36. package/amxmodx/base/include/gameconfig.inc +93 -0
  37. package/amxmodx/base/include/geoip.inc +264 -0
  38. package/amxmodx/base/include/ham_const.inc +4352 -0
  39. package/amxmodx/base/include/hamsandwich.inc +439 -0
  40. package/amxmodx/base/include/hlsdk_const.inc +808 -0
  41. package/amxmodx/base/include/json.inc +777 -0
  42. package/amxmodx/base/include/lang.inc +119 -0
  43. package/amxmodx/base/include/message_const.inc +1130 -0
  44. package/amxmodx/base/include/message_stocks.inc +161 -0
  45. package/amxmodx/base/include/messages.inc +604 -0
  46. package/amxmodx/base/include/newmenus.inc +387 -0
  47. package/amxmodx/base/include/ns.inc +754 -0
  48. package/amxmodx/base/include/ns_const.inc +196 -0
  49. package/amxmodx/base/include/nvault.inc +154 -0
  50. package/amxmodx/base/include/regex.inc +348 -0
  51. package/amxmodx/base/include/sockets.inc +183 -0
  52. package/amxmodx/base/include/sorting.inc +107 -0
  53. package/amxmodx/base/include/sqlx.inc +582 -0
  54. package/amxmodx/base/include/string.inc +786 -0
  55. package/amxmodx/base/include/string_const.inc +156 -0
  56. package/amxmodx/base/include/string_stocks.inc +325 -0
  57. package/amxmodx/base/include/textparse_ini.inc +214 -0
  58. package/amxmodx/base/include/textparse_smc.inc +261 -0
  59. package/amxmodx/base/include/tfcconst.inc +84 -0
  60. package/amxmodx/base/include/tfcstats.inc +62 -0
  61. package/amxmodx/base/include/tfcx.inc +148 -0
  62. package/amxmodx/base/include/time.inc +118 -0
  63. package/amxmodx/base/include/tsconst.inc +154 -0
  64. package/amxmodx/base/include/tsfun.inc +198 -0
  65. package/amxmodx/base/include/tsstats.inc +65 -0
  66. package/amxmodx/base/include/tsx.inc +88 -0
  67. package/amxmodx/base/include/vault.inc +60 -0
  68. package/amxmodx/base/include/vector.inc +131 -0
  69. package/amxmodx/base/include/xs.inc +1570 -0
  70. package/as/amxts.d.ts +153 -0
  71. package/as/constants.ts +4730 -0
  72. package/as/effects.ts +1449 -0
  73. package/as/entities.ts +5482 -0
  74. package/as/events.ts +3054 -0
  75. package/as/facade.ts +4639 -0
  76. package/as/flags.ts +299 -0
  77. package/as/fs.ts +287 -0
  78. package/as/hooks.ts +18536 -0
  79. package/as/kit.ts +25 -0
  80. package/as/lib/check.ts +86 -0
  81. package/as/modules/http.ts +184 -0
  82. package/as/natives.ts +11845 -0
  83. package/as/os.ts +30 -0
  84. package/as/promise.ts +1392 -0
  85. package/as/promise.types.d.ts +82 -0
  86. package/as/remote.ts +288 -0
  87. package/as/tsconfig.json +22 -0
  88. package/as/vector.ts +65 -0
  89. package/bin/amxts.mjs +10 -0
  90. package/includes/menu_core.inc +355 -0
  91. package/includes/order.txt +54 -0
  92. package/includes/sources.json +27 -0
  93. package/includes/universal_config.inc +355 -0
  94. package/lint/oxlint-plugin.mjs +77 -0
  95. package/node_modules/assemblyscript/LICENSE +201 -0
  96. package/node_modules/assemblyscript/NOTICE +93 -0
  97. package/node_modules/assemblyscript/package.json +63 -0
  98. package/node_modules/assemblyscript/std/README.md +6 -0
  99. package/node_modules/assemblyscript/std/assembly/array.ts +526 -0
  100. package/node_modules/assemblyscript/std/assembly/arraybuffer.ts +77 -0
  101. package/node_modules/assemblyscript/std/assembly/atomics.ts +127 -0
  102. package/node_modules/assemblyscript/std/assembly/bindings/asyncify.ts +16 -0
  103. package/node_modules/assemblyscript/std/assembly/bindings/dom.ts +291 -0
  104. package/node_modules/assemblyscript/std/assembly/bindings/node.ts +6 -0
  105. package/node_modules/assemblyscript/std/assembly/builtins.ts +2631 -0
  106. package/node_modules/assemblyscript/std/assembly/compat.ts +2 -0
  107. package/node_modules/assemblyscript/std/assembly/console.ts +42 -0
  108. package/node_modules/assemblyscript/std/assembly/crypto.ts +9 -0
  109. package/node_modules/assemblyscript/std/assembly/dataview.ts +181 -0
  110. package/node_modules/assemblyscript/std/assembly/date.ts +373 -0
  111. package/node_modules/assemblyscript/std/assembly/diagnostics.ts +11 -0
  112. package/node_modules/assemblyscript/std/assembly/error.ts +44 -0
  113. package/node_modules/assemblyscript/std/assembly/function.ts +38 -0
  114. package/node_modules/assemblyscript/std/assembly/index.d.ts +2822 -0
  115. package/node_modules/assemblyscript/std/assembly/iterator.ts +35 -0
  116. package/node_modules/assemblyscript/std/assembly/map.ts +260 -0
  117. package/node_modules/assemblyscript/std/assembly/math.ts +3289 -0
  118. package/node_modules/assemblyscript/std/assembly/memory.ts +123 -0
  119. package/node_modules/assemblyscript/std/assembly/number.ts +388 -0
  120. package/node_modules/assemblyscript/std/assembly/object.ts +36 -0
  121. package/node_modules/assemblyscript/std/assembly/performance.ts +9 -0
  122. package/node_modules/assemblyscript/std/assembly/polyfills.ts +27 -0
  123. package/node_modules/assemblyscript/std/assembly/process.ts +50 -0
  124. package/node_modules/assemblyscript/std/assembly/reference.ts +48 -0
  125. package/node_modules/assemblyscript/std/assembly/regexp.ts +12 -0
  126. package/node_modules/assemblyscript/std/assembly/rt/README.md +83 -0
  127. package/node_modules/assemblyscript/std/assembly/rt/common.ts +81 -0
  128. package/node_modules/assemblyscript/std/assembly/rt/index-incremental.ts +2 -0
  129. package/node_modules/assemblyscript/std/assembly/rt/index-minimal.ts +2 -0
  130. package/node_modules/assemblyscript/std/assembly/rt/index-stub.ts +1 -0
  131. package/node_modules/assemblyscript/std/assembly/rt/index.d.ts +37 -0
  132. package/node_modules/assemblyscript/std/assembly/rt/itcms.ts +419 -0
  133. package/node_modules/assemblyscript/std/assembly/rt/rtrace.ts +15 -0
  134. package/node_modules/assemblyscript/std/assembly/rt/stub.ts +133 -0
  135. package/node_modules/assemblyscript/std/assembly/rt/tcms.ts +254 -0
  136. package/node_modules/assemblyscript/std/assembly/rt/tlsf.ts +592 -0
  137. package/node_modules/assemblyscript/std/assembly/rt.ts +87 -0
  138. package/node_modules/assemblyscript/std/assembly/set.ts +220 -0
  139. package/node_modules/assemblyscript/std/assembly/shared/feature.ts +65 -0
  140. package/node_modules/assemblyscript/std/assembly/shared/runtime.ts +11 -0
  141. package/node_modules/assemblyscript/std/assembly/shared/target.ts +11 -0
  142. package/node_modules/assemblyscript/std/assembly/shared/tsconfig.json +11 -0
  143. package/node_modules/assemblyscript/std/assembly/shared/typeinfo.ts +72 -0
  144. package/node_modules/assemblyscript/std/assembly/staticarray.ts +401 -0
  145. package/node_modules/assemblyscript/std/assembly/string.ts +847 -0
  146. package/node_modules/assemblyscript/std/assembly/symbol.ts +114 -0
  147. package/node_modules/assemblyscript/std/assembly/table.ts +16 -0
  148. package/node_modules/assemblyscript/std/assembly/tsconfig.json +6 -0
  149. package/node_modules/assemblyscript/std/assembly/typedarray.ts +1945 -0
  150. package/node_modules/assemblyscript/std/assembly/uri.ts +17 -0
  151. package/node_modules/assemblyscript/std/assembly/util/bytes.ts +107 -0
  152. package/node_modules/assemblyscript/std/assembly/util/casemap.ts +497 -0
  153. package/node_modules/assemblyscript/std/assembly/util/error.ts +54 -0
  154. package/node_modules/assemblyscript/std/assembly/util/hash.ts +117 -0
  155. package/node_modules/assemblyscript/std/assembly/util/math.ts +1922 -0
  156. package/node_modules/assemblyscript/std/assembly/util/memory.ts +290 -0
  157. package/node_modules/assemblyscript/std/assembly/util/number.ts +873 -0
  158. package/node_modules/assemblyscript/std/assembly/util/sort.ts +313 -0
  159. package/node_modules/assemblyscript/std/assembly/util/string.ts +1202 -0
  160. package/node_modules/assemblyscript/std/assembly/util/uri.ts +275 -0
  161. package/node_modules/assemblyscript/std/assembly/vector.ts +4 -0
  162. package/node_modules/assemblyscript/std/assembly.json +16 -0
  163. package/node_modules/assemblyscript/std/portable/index.d.ts +462 -0
  164. package/node_modules/assemblyscript/std/portable/index.js +416 -0
  165. package/node_modules/assemblyscript/std/portable.json +11 -0
  166. package/node_modules/assemblyscript/std/types/assembly/index.d.ts +1 -0
  167. package/node_modules/assemblyscript/std/types/assembly/package.json +3 -0
  168. package/node_modules/assemblyscript/std/types/portable/index.d.ts +1 -0
  169. package/node_modules/assemblyscript/std/types/portable/package.json +3 -0
  170. package/node_modules/assemblyscript/tsconfig-base.json +13 -0
  171. package/package.json +112 -0
  172. package/runtime/deps/assemblyscript/LICENSE +201 -0
  173. package/runtime/deps/assemblyscript/NOTICE +93 -0
  174. package/runtime/deps/assemblyscript/bin/asc.js +35 -0
  175. package/runtime/deps/assemblyscript/dist/asc.js +25035 -0
  176. package/runtime/deps/assemblyscript/dist/assemblyscript.js +337 -0
  177. package/runtime/deps/assemblyscript/node_modules/binaryen/LICENSE +201 -0
  178. package/runtime/deps/assemblyscript/node_modules/binaryen/index.js +28905 -0
  179. package/runtime/deps/assemblyscript/node_modules/binaryen/package.json +50 -0
  180. package/runtime/deps/assemblyscript/node_modules/long/LICENSE +202 -0
  181. package/runtime/deps/assemblyscript/node_modules/long/index.js +1581 -0
  182. package/runtime/deps/assemblyscript/node_modules/long/package.json +58 -0
  183. package/runtime/deps/assemblyscript/package.json +114 -0
  184. package/runtime/host/amxts.inc +85 -0
  185. package/runtime/licenses/AMXX-LICENSE.txt +34 -0
  186. package/runtime/licenses/AssemblyScript-LICENSE +201 -0
  187. package/runtime/licenses/AssemblyScript-NOTICE +93 -0
  188. package/runtime/licenses/Binaryen-LICENSE +201 -0
  189. package/runtime/licenses/Bun-LICENSE.md +84 -0
  190. package/runtime/licenses/GPL-3.0.txt +674 -0
  191. package/runtime/licenses/LLVM-LICENSE.TXT +279 -0
  192. package/runtime/licenses/README.md +25 -0
  193. package/runtime/licenses/WAMR-LICENSE +219 -0
  194. package/runtime/natives.txt +1095 -0
  195. package/runtime/patches/README.md +59 -0
  196. package/runtime/patches/assemblyscript-0.28.20-amxts.patch +14174 -0
  197. package/runtime/patches/wamr-2.4.5-amxts.patch +364 -0
  198. package/scripts/apply-docs.ts +508 -0
  199. package/scripts/asc.ts +27 -0
  200. package/scripts/auto-imports.ts +258 -0
  201. package/scripts/build-wasm.ts +595 -0
  202. package/scripts/check.ts +93 -0
  203. package/scripts/compile-cache.ts +179 -0
  204. package/scripts/compile.ts +401 -0
  205. package/scripts/docker-server.ts +73 -0
  206. package/scripts/docs/as/amxts.ts +210 -0
  207. package/scripts/docs/as/effects.ts +1684 -0
  208. package/scripts/docs/as/facade.ts +3302 -0
  209. package/scripts/docs/as/fs.ts +173 -0
  210. package/scripts/docs/as/lib/check.ts +68 -0
  211. package/scripts/docs/as/modules/http.ts +66 -0
  212. package/scripts/docs/as/os.ts +16 -0
  213. package/scripts/docs/as/promise.ts +132 -0
  214. package/scripts/docs/as/promise.types.ts +63 -0
  215. package/scripts/docs/as/vector.ts +48 -0
  216. package/scripts/docs/entities.ts +2338 -0
  217. package/scripts/docs/events.ts +245 -0
  218. package/scripts/docs/game.ts +497 -0
  219. package/scripts/docs/messages.ts +148 -0
  220. package/scripts/file-contents.ts +74 -0
  221. package/scripts/includes.ts +134 -0
  222. package/scripts/player-fields.ts +866 -0
  223. package/scripts/plugin-cache.ts +92 -0
  224. package/scripts/plugin-natives.ts +1157 -0
  225. package/scripts/prebuilt.ts +268 -0
  226. package/scripts/prepare.ts +134 -0
  227. package/scripts/project.ts +973 -0
  228. package/scripts/run.ts +28 -0
  229. package/scripts/shared-modules.ts +906 -0
  230. package/scripts/system.ts +33 -0
  231. package/scripts/tracked-fs.ts +115 -0
  232. package/scripts/typed-configs.ts +788 -0
  233. package/scripts/ui.ts +77 -0
  234. package/src/cli-api.mjs +175 -0
  235. package/src/parser/include-parser.ts +506 -0
  236. package/src/system.mjs +155 -0
  237. package/src/testing/compile-cache.ts +31 -0
  238. package/src/testing/compile.ts +222 -0
  239. package/src/testing/coroutines.ts +191 -0
  240. package/src/testing/index.ts +39 -0
  241. package/src/testing/kits.ts +55 -0
  242. package/src/testing/memory.ts +125 -0
  243. package/src/testing/natives.ts +925 -0
  244. package/src/testing/server.ts +1874 -0
  245. package/src/testing/setup.ts +63 -0
  246. package/src/testing/tables.ts +139 -0
  247. package/src/types.ts +51 -0
@@ -0,0 +1,1157 @@
1
+ import type { ForwardDeclaration, NativeFunction, Parameter } from '../src/types';
2
+ // A plugin's own natives: every `export function` of its entry file becomes an
3
+ // AMX Mod X native under the same name, with its real types.
4
+ //
5
+ // export function cfg_get_value(file: string, key: string) { return ...; }
6
+ // // native cfg_get_value(const file[], const key[], out[], len);
7
+ //
8
+ // Two pieces, both run from scripts/compile.ts (and src/testing/compile.ts):
9
+ //
10
+ // - an AssemblyScript transform. After parsing it reads the entry file's
11
+ // exported functions and adds a hidden entry beside it with one wrapper per
12
+ // function: the wrapper reads the Pawn caller's arguments through the
13
+ // facade's __native* helpers, calls the function and hands the result back,
14
+ // and registers itself under the function's name. After compiling it reads
15
+ // the result types the compiler inferred - a native's return type is not
16
+ // written, like any other function's.
17
+ // - pawnInclude(), the `.inc` a Pawn plugin includes to call them.
18
+ //
19
+ // Nothing here changes the compiler: the wrappers are ordinary AssemblyScript
20
+ // against the facade, and a plugin that exports nothing gets nothing.
21
+ //
22
+ // A plugin that implements an existing Pawn include says so -
23
+ // `plugin({ ..., include: "myplugin.inc" })` - and that include is the
24
+ // contract: its declarations say how every argument crosses (a `Float:`, a
25
+ // tag, an out-buffer before the other parameters, a `&ref`, an `any:...`
26
+ // tail), so the TypeScript function keeps plain TypeScript types. See
27
+ // contractPlan() and docs/en/6.pawn/01.natives.md.
28
+ import { basename, dirname, resolve } from 'node:path';
29
+ // @ts-ignore - shipped as JavaScript, with types beside it we do not need here
30
+ import * as asc from '../runtime/deps/assemblyscript/dist/assemblyscript.js';
31
+ import { IncludeParser } from '../src/parser/include-parser';
32
+ import { listIncludes, readInclude } from './includes';
33
+ import { sourcesFor } from './project';
34
+ import { existsSync, readFileSync } from './tracked-fs';
35
+
36
+ /**
37
+ * How one argument crosses from Pawn. `tag` is a cell under one of the
38
+ * plugin's own exported enums - `section: ConfigSection` is
39
+ * `ConfigSection:section` in the include. `player` is a player id read as a
40
+ * Player: a cell that is not a player slot (1 to maxPlayers) never reaches
41
+ * the function - the native answers its result's default (0, false, "")
42
+ * instead. `player?` is `Player | null`, where 0 - the server, everyone -
43
+ * is null.
44
+ */
45
+ export type ParamKind = 'string' | 'int' | 'float' | 'bool' | 'ints' | 'floats' | 'vector' | 'tag' | 'player' | 'player?' | 'team';
46
+
47
+ /**
48
+ * How the result goes back. `string?` is `string | null`: a bool, and the
49
+ * text in `out[]`. `tag` is a cell under an exported enum; `array` is a
50
+ * facade CellArray - an AMX Mod X `Array:` handle the caller destroys, or
51
+ * Invalid_Array for null.
52
+ */
53
+ export type ResultKind = 'void' | 'int' | 'float' | 'bool' | 'string' | 'string?' | 'ints' | 'floats' | 'tag' | 'array';
54
+
55
+ export interface NativeParam {
56
+ name: string;
57
+ kind: ParamKind;
58
+ /** The Pawn default, when the TypeScript one is a literal Pawn can write. */
59
+ pawnDefault: string | null;
60
+ /** The enum a `tag` parameter is typed with. */
61
+ tag?: string;
62
+ }
63
+
64
+ export interface PluginNative {
65
+ name: string;
66
+ params: NativeParam[];
67
+ result: ResultKind;
68
+ /** The enum a `tag` result is typed with. */
69
+ resultTag?: string;
70
+ /** The function's own `/** ... *\/` comment, for the include. */
71
+ doc: string;
72
+ /**
73
+ * For a native the plugin's include declares: the Pawn arguments in order,
74
+ * as that declaration lays them out (see contractPlan).
75
+ */
76
+ layout?: PawnSlot[];
77
+ /** Its declaration's return tag: `Float`, `bool`, another tag or `any`. */
78
+ pawnResult?: string;
79
+ /** The declaration itself, for the include the build writes. */
80
+ declaration?: NativeFunction;
81
+ }
82
+
83
+ /**
84
+ * What a native writes back through an argument: text into `x[], len`, cells
85
+ * into `x[], size`, one cell through `&x` (`Float:&x` - a float).
86
+ */
87
+ export type OutputKind = 'string' | 'ints' | 'floats' | 'ref' | 'float-ref';
88
+
89
+ /** An `export enum` of the entry file: a Pawn enum in the include, and a tag. */
90
+ export interface PluginEnum {
91
+ name: string;
92
+ members: { name: string; value: number }[];
93
+ doc: string;
94
+ }
95
+
96
+ // The enums of a compile, beside its natives: filled by the transform, read
97
+ // by pawnInclude() - both are handed the same natives array.
98
+ const ENUMS = new WeakMap<PluginNative[], PluginEnum[]>();
99
+
100
+ /** The `export enum`s that came with these natives. */
101
+ export function nativeEnums(natives: PluginNative[]): PluginEnum[] {
102
+ return ENUMS.get(natives) ?? [];
103
+ }
104
+
105
+ /** The hidden entry the wrappers live in, beside the plugin's entry file. */
106
+ const WRAPPERS = '__amxts_natives.ts';
107
+
108
+ // The arguments the module reads from a Pawn call of a plugin's native: the
109
+ // rest would be lost. Must match MAX_NATIVE_ARGS in runtime/src/module.cpp.
110
+ const MAX_NATIVE_ARGS = 64;
111
+
112
+ /** The Pawn include a plugin implements: its text, and its natives by name. */
113
+ export interface Contract {
114
+ /** The name the plugin gave: "myplugin.inc". */
115
+ file: string;
116
+ text: string;
117
+ natives: Map<string, NativeFunction>;
118
+ }
119
+
120
+ // The contract of a compile, beside its natives - read by pawnInclude().
121
+ const CONTRACTS = new WeakMap<PluginNative[], Contract>();
122
+ // The author the plugin names in plugin({ author }), for its include's header.
123
+ const AUTHORS = new WeakMap<PluginNative[], string>();
124
+
125
+ /** The include these natives implement, when the plugin named one. */
126
+ export function nativeContract(natives: PluginNative[]): Contract | undefined {
127
+ return CONTRACTS.get(natives);
128
+ }
129
+
130
+ /** What a compile keeps beside its natives, as data: for a cache to store with them. */
131
+ export interface NativesBeside {
132
+ enums: PluginEnum[];
133
+ contract: { file: string; text: string } | null;
134
+ author: string | null;
135
+ }
136
+
137
+ export function nativesBeside(natives: PluginNative[]): NativesBeside {
138
+ const contract = CONTRACTS.get(natives);
139
+ return { enums: nativeEnums(natives), contract: contract ? { file: contract.file, text: contract.text } : null, author: AUTHORS.get(natives) ?? null };
140
+ }
141
+
142
+ /** Natives read back from a cache get their enums and contract beside them again. */
143
+ export function setNativesBeside(natives: PluginNative[], beside: NativesBeside) {
144
+ ENUMS.set(natives, beside.enums);
145
+ if (beside.contract) CONTRACTS.set(natives, contractOf(beside.contract.file, beside.contract.text));
146
+ if (beside.author) AUTHORS.set(natives, beside.author);
147
+ }
148
+
149
+ function contractOf(file: string, text: string): Contract {
150
+ return { file, text, natives: new Map(new IncludeParser(text).parse().natives.map(native => [native.name, native])) };
151
+ }
152
+
153
+ /** The text fields of `plugin({ ... })` at the top of the entry file: `include`, `author`, ... */
154
+ function pluginInfo(source: any): Record<string, string> {
155
+ const info: Record<string, string> = {};
156
+ for (const statement of source.statements) {
157
+ const call = statement.kind === asc.NodeKind.Expression ? statement.expression : null;
158
+ if (!call || call.kind !== asc.NodeKind.Call || call.expression.kind !== asc.NodeKind.Identifier) continue;
159
+ if (call.expression.text !== 'plugin' || call.args.length !== 1) continue;
160
+ const fields = call.args[0];
161
+ if (fields.kind !== asc.NodeKind.Literal || fields.literalKind !== asc.LiteralKind.Object) continue;
162
+ fields.names.forEach((key: any, i: number) => {
163
+ const value = fields.values[i];
164
+ if (value.kind === asc.NodeKind.Literal && value.literalKind === asc.LiteralKind.String) info[key.text] = value.value;
165
+ });
166
+ }
167
+ return info;
168
+ }
169
+
170
+ /**
171
+ * `plugin({ ..., include: "myplugin.inc" })` at the top of the entry file:
172
+ * the include is looked for beside the plugin, in the plugins folder, in the
173
+ * project's includes/ and in the core's - where the includes of the Pawn
174
+ * plugins it stands in for already are.
175
+ */
176
+ function readContract(source: any, entry: string, root: string, problems: string[]): Contract | null {
177
+ const file = pluginInfo(source).include ?? null;
178
+ // A module package that says `"contract": true` has its include as the
179
+ // contract, whether or not its natives name it in plugin({ include }).
180
+ const sources = sourcesFor(root);
181
+ const contract = sources.contractOf(entry);
182
+ if (!file && contract) {
183
+ return contractOf(basename(contract), readFileSync(contract, 'utf-8'));
184
+ }
185
+ if (!file) return null;
186
+
187
+ const base = resolve(root);
188
+ const candidates = [resolve(base, dirname(entry), file), ...sources.includeCandidates(entry, file), resolve(base, '..', 'includes', file)];
189
+ const path = candidates.find(existsSync);
190
+ if (!path) {
191
+ problems.push(`${source.normalizedPath}: include "${file}" is not beside the plugin, in its package, in ${base} or in an includes/ folder`);
192
+ return null;
193
+ }
194
+
195
+ return contractOf(file, readFileSync(path, 'utf-8'));
196
+ }
197
+
198
+ /** Exports that are not natives: the module's own hook, and the hood's names. */
199
+ function isNative(name: string): boolean {
200
+ return name !== 'init' && !name.startsWith('__');
201
+ }
202
+
203
+ /** `number`, `Float`, `string | null`, `Float[]` - a type node as a name. */
204
+ function typeName(node: any): { name: string; args: any[]; nullable: boolean } | null {
205
+ if (!node || node.kind !== asc.NodeKind.NamedType) return null;
206
+ return { name: node.name.identifier.text, args: node.typeArguments ?? [], nullable: node.isNullable };
207
+ }
208
+
209
+ function paramKind(node: any, tags: Set<string> = new Set()): ParamKind | null {
210
+ const type = typeName(node);
211
+ if (type?.name === 'Player') return type.nullable ? 'player?' : 'player';
212
+ if (!type || type.nullable) return null;
213
+ if (tags.has(type.name)) return 'tag';
214
+ if (type.name === 'string') return 'string';
215
+ if (type.name === 'number') return 'int';
216
+ if (type.name === 'Float') return 'float';
217
+ if (type.name === 'boolean' || type.name === 'bool') return 'bool';
218
+ if (type.name === 'Vector') return 'vector';
219
+ if (type.name === 'Array' && type.args.length === 1) {
220
+ const element = typeName(type.args[0]);
221
+ if (element?.name === 'number' && !element.nullable) return 'ints';
222
+ if (element?.name === 'Float' && !element.nullable) return 'floats';
223
+ }
224
+ return null;
225
+ }
226
+
227
+ /** A written result type, or null when it is not one a native can have. */
228
+ function resultKind(node: any, tags: Set<string> = new Set()): ResultKind | null {
229
+ const type = typeName(node);
230
+ if (!type) return null;
231
+ if (type.name === 'void') return 'void';
232
+ if (type.name === 'string') return type.nullable ? 'string?' : 'string';
233
+ if (type.name === 'CellArray') return 'array';
234
+ if (type.nullable) return null;
235
+ const kind = paramKind(node, tags);
236
+ // A Player or a Team as a written result is left as the kind it is.
237
+ return (kind === 'vector' ? 'floats' : kind) as ResultKind | null;
238
+ }
239
+
240
+ /** The result type the compiler settled on, for a function whose type is not written. */
241
+ function inferredKind(type: any): ResultKind | null {
242
+ const text = String(type.toString());
243
+ if (text === 'void') return 'void';
244
+ if (text === 'bool') return 'bool';
245
+ if (/^[iu](?:8|16|32|64)$|^f(?:32|64)$|^[iu]size$/.test(text)) return 'int';
246
+ if (/(?:^|\/)String(?: \| null)?$/.test(text)) return text.endsWith('| null') ? 'string?' : 'string';
247
+ if (/(?:^|\/)Array<f64>$/.test(text)) return 'ints';
248
+ if (/(?:^|\/)CellArray(?: \| null)?$/.test(text)) return 'array';
249
+ return null;
250
+ }
251
+
252
+ /** TeamName by number: TEAM_UNASSIGNED 0, TERRORIST 1, CT 2, SPECTATOR 3 - as the facade's TEAM_NAMES. */
253
+ const TEAM_NAMES = ['UNASSIGNED', 'TERRORIST', 'CT', 'SPECTATOR'];
254
+
255
+ /**
256
+ * `errorKey?: string`: a string Pawn passes, "" when the caller passed
257
+ * nothing, so the parameter is a string all the same - null only in the
258
+ * TypeScript that calls the function itself.
259
+ */
260
+ function isOptionalString(parameter: any): boolean {
261
+ const type = typeName(parameter.type);
262
+ return type?.name === 'string' && type.nullable && parameter.initializer?.kind === asc.NodeKind.Null;
263
+ }
264
+
265
+ function pawnDefault(kind: ParamKind, initializer: any): string | null {
266
+ if (!initializer) return null;
267
+ const text = String(asc.ASTBuilder.build(initializer)).trim();
268
+
269
+ if (kind === 'player?' && text === 'null') return '0';
270
+ if (kind === 'string' && initializer.kind === asc.NodeKind.Null) return '""';
271
+ if (kind === 'team' && initializer.kind === asc.NodeKind.Literal && TEAM_NAMES.includes(initializer.value)) return String(TEAM_NAMES.indexOf(initializer.value));
272
+ if ((kind === 'int' || kind === 'tag') && /^-?\d+$/.test(text)) return text;
273
+ if (kind === 'float' && /^-?\d+(?:\.\d+)?$/.test(text)) return text.includes('.') ? text : `${text}.0`;
274
+ if (kind === 'bool' && /^(?:true|false)$/.test(text)) return text;
275
+ if (kind === 'string' && initializer.kind === asc.NodeKind.Literal && typeof initializer.value === 'string') {
276
+ // Pawn's escape character is ^, not \.
277
+ const value = initializer.value.replace(/\^/g, '^^').replace(/"/g, '^"').replace(/\n/g, '^n').replace(/\t/g, '^t');
278
+ return `"${value}"`;
279
+ }
280
+ return null;
281
+ }
282
+
283
+ function docOf(text: string, start: number): string {
284
+ // The comment that ends right before the declaration, and only that one.
285
+ const before = text.slice(0, start);
286
+ const end = before.search(/\*\/\s*(?:export\s+)?$/);
287
+ if (end < 0) return '';
288
+ const open = before.lastIndexOf('/**', end);
289
+ return open < 0 ? '' : before.slice(open, end + 2);
290
+ }
291
+
292
+ /**
293
+ * Where a parameter's cells start: an array takes two (the array and its
294
+ * size, as Pawn passes it), everything else one.
295
+ *
296
+ * A string or array result adds two more - `out[], len` - and they go where
297
+ * Pawn puts them: before the first parameter with a default, so that the
298
+ * optional arguments stay last (`value[], len, index = 0`), or at the end
299
+ * when there is none. `out` is that place and `split` how many parameters
300
+ * come before it. The cells after it are two further on only when the buffer
301
+ * is there, and the wrapper learns that from how many cells the caller
302
+ * passed: for a result type that is not written, whether it is a string is
303
+ * known only once the compile is over, after the wrappers are written.
304
+ */
305
+ function indexes(params: NativeParam[]): { at: number[]; out: number; split: number; cells: number } {
306
+ const at: number[] = [];
307
+ let next = 0;
308
+ let out = -1;
309
+ let split = params.length;
310
+ params.forEach((param, i) => {
311
+ if (out < 0 && param.pawnDefault !== null) {
312
+ out = next;
313
+ split = i;
314
+ }
315
+ at.push(next);
316
+ next += param.kind === 'ints' || param.kind === 'floats' ? 2 : 1;
317
+ });
318
+ return { at, out: out < 0 ? next : out, split, cells: next };
319
+ }
320
+
321
+ /**
322
+ * A Pawn call's arguments in order: each parameter, and the result's buffer.
323
+ * A contract native also has `output`s - the arguments it writes through, in
324
+ * order - and parameters that come from the `any:...` tail (`byRef`: Pawn
325
+ * passes the tail by address), or a `format` tail the string before it is
326
+ * formatted with.
327
+ */
328
+ export type PawnSlot
329
+ = | { param: NativeParam; index: number; byRef?: boolean }
330
+ | { out: true }
331
+ | { output: OutputKind }
332
+ | { format: true };
333
+
334
+ /** How many arguments a Pawn call of the native passes: an array or an out-buffer is two, itself and its size. */
335
+ function pawnArgCount(native: PluginNative): number {
336
+ if (native.declaration) return native.declaration.params.length;
337
+ return pawnLayout(native).reduce((count, slot) =>
338
+ count + ('out' in slot || ('param' in slot && (slot.param.kind === 'ints' || slot.param.kind === 'floats')) ? 2 : 1), 0);
339
+ }
340
+
341
+ /** The order a Pawn plugin passes a native's arguments in - see indexes(). */
342
+ export function pawnLayout(native: PluginNative): PawnSlot[] {
343
+ if (native.layout) return native.layout;
344
+ const buffer = ['string', 'string?', 'ints', 'floats'].includes(native.result);
345
+ const { split } = indexes(native.params);
346
+ const slots: PawnSlot[] = native.params.map((param, index) => ({ param, index }));
347
+ if (buffer) slots.splice(split, 0, { out: true });
348
+ return slots;
349
+ }
350
+
351
+ const READERS: Record<ParamKind, string> = {
352
+ 'string': '__nativeString',
353
+ 'int': '__nativeInt',
354
+ 'float': '__nativeFloat',
355
+ 'bool': '__nativeBool',
356
+ 'ints': '__nativeInts',
357
+ 'floats': '__nativeFloats',
358
+ 'vector': '__nativeVector',
359
+ 'tag': '__nativeCell',
360
+ 'player': '__nativePlayer',
361
+ 'player?': '__nativePlayer',
362
+ 'team': '__nativeTeam',
363
+ };
364
+
365
+ /** How the wrapper reads a parameter at `at`: a Player that passed its check is not null. */
366
+ function readOf(kind: ParamKind, at: string): string {
367
+ return kind === 'player' ? `__nativePlayer(${at})!` : `${READERS[kind]}(${at})`;
368
+ }
369
+
370
+ /**
371
+ * The check a wrapper makes before it calls the function, when a parameter
372
+ * is a Player or a Team: every such cell a player slot (or 0 where null is
373
+ * taken), a TeamName number 0 to 3.
374
+ */
375
+ function playerCheck(params: NativeParam[], at: (i: number) => string): string | null {
376
+ const checks = params.flatMap((param, i) =>
377
+ param.kind === 'player' || param.kind === 'player?'
378
+ ? [`!__nativeTarget(${at(i)}, ${param.kind === 'player?'})`]
379
+ : param.kind === 'team' ? [`!__nativeIsTeam(${at(i)})`] : []);
380
+ return checks.length ? checks.join(' || ') : null;
381
+ }
382
+
383
+ /**
384
+ * A contract native's arguments, as its Pawn declaration lays them out, and
385
+ * how the wrapper reads each TypeScript parameter from them.
386
+ *
387
+ * The TypeScript parameters take the declaration's inputs in order: a
388
+ * `Float:` is read as a float, `bool:` as a boolean, any other tag as a cell,
389
+ * `const x[]` as text (or, for a `number[]` parameter, an array and the size
390
+ * after it), `const Float:x[3]` as a Vector. What the native writes through -
391
+ * `x[]` with the length after it, `&x` - is an output, filled from the
392
+ * function's result. An `any:...` tail is either the TypeScript parameters
393
+ * left over (text, or a number Pawn passes by address), or, when there are
394
+ * none and the tail follows a string, that string formatted with it the way
395
+ * format() does - `%L` included.
396
+ */
397
+ interface ContractPlan {
398
+ layout: PawnSlot[];
399
+ params: NativeParam[];
400
+ /** How the wrapper reads each TypeScript parameter. */
401
+ reads: string[];
402
+ /** Each output: its cell, and what is written there. */
403
+ outputs: { at: number; kind: OutputKind }[];
404
+ /** Each parameter's first cell, for the Player check. */
405
+ starts: number[];
406
+ }
407
+
408
+ /** A TypeScript default as the wrapper writes it, or null when it is not a literal. */
409
+ function literalDefault(initializer: any): string | null {
410
+ if (!initializer) return null;
411
+ if (initializer.kind !== asc.NodeKind.Literal && initializer.kind !== asc.NodeKind.UnaryPrefix
412
+ && initializer.kind !== asc.NodeKind.True && initializer.kind !== asc.NodeKind.False) {
413
+ return null;
414
+ }
415
+ return String(asc.ASTBuilder.build(initializer)).trim();
416
+ }
417
+
418
+ function contractPlan(declaration: NativeFunction, parameters: any[], where: string, problems: string[]): ContractPlan | null {
419
+ const pawn = declaration.params;
420
+ const layout: PawnSlot[] = [];
421
+ const params: NativeParam[] = [];
422
+ const reads: string[] = [];
423
+ const outputs: { at: number; kind: OutputKind }[] = [];
424
+ const starts: number[] = [];
425
+ let at = 0;
426
+ let next = 0;
427
+ let lastString = -1;
428
+
429
+ const isLength = (param: Parameter | undefined) => param !== undefined && !param.isArray && !param.isRef && !param.isRest;
430
+
431
+ for (let i = 0; i < pawn.length; i++) {
432
+ const param = pawn[i];
433
+
434
+ if (param.isRest) {
435
+ if (next < parameters.length) {
436
+ // The tail is the TypeScript parameters that are left: Pawn passes
437
+ // each by address, so a number is read through it.
438
+ for (; next < parameters.length; next++, at++) {
439
+ const node = parameters[next];
440
+ const type = typeName(node.type)?.name;
441
+ const kind: ParamKind | null = type === 'string' ? 'string' : type === 'number' ? 'int' : type === 'boolean' ? 'bool' : null;
442
+ if (!kind) {
443
+ problems.push(`${where} - parameter "${node.name.text}": the any:... tail takes string, number or boolean`);
444
+ return null;
445
+ }
446
+ const fallback = literalDefault(node.initializer) ?? (kind === 'string' ? '""' : kind === 'bool' ? 'false' : '0');
447
+ const read = kind === 'string' ? `__nativeString(${at})` : kind === 'bool' ? `__nativeRef(${at}) != 0` : `__nativeRef(${at})`;
448
+ reads.push(`(__nativeCount() > ${at} ? ${read} : ${fallback})`);
449
+ params.push({ name: node.name.text, kind, pawnDefault: pawnDefault(kind, node.initializer) });
450
+ starts.push(at);
451
+ layout.push({ param: params[params.length - 1], index: next, byRef: true });
452
+ }
453
+ } else if (lastString >= 0 && lastString === reads.length - 1) {
454
+ // Nothing takes the tail: the string before it is a format, and
455
+ // the function gets it formatted.
456
+ reads[lastString] = reads[lastString].replace('__nativeString(', '__nativeFormat(');
457
+ layout.push({ format: true });
458
+ }
459
+ break;
460
+ }
461
+
462
+ if (param.isRef) {
463
+ const kind: OutputKind = param.type === 'Float' ? 'float-ref' : 'ref';
464
+ outputs.push({ at, kind });
465
+ layout.push({ output: kind });
466
+ at++;
467
+ continue;
468
+ }
469
+
470
+ if (param.isArray && !param.isConst) {
471
+ if (!isLength(pawn[i + 1])) {
472
+ problems.push(`${where} - ${param.name}[]: an output array needs its length right after it`);
473
+ return null;
474
+ }
475
+ const kind: OutputKind = param.type === 'Float' ? 'floats' : 'string';
476
+ outputs.push({ at, kind });
477
+ layout.push({ output: kind });
478
+ at += 2;
479
+ i++;
480
+ continue;
481
+ }
482
+
483
+ const node = parameters[next];
484
+ if (!node) {
485
+ problems.push(`${where} - takes ${next} parameters, and the include declares more: ${param.name} has none to go to`);
486
+ return null;
487
+ }
488
+ const type = typeName(node.type);
489
+ const tsName = type?.name ?? '';
490
+ const element = type?.name === 'Array' && type.args.length === 1 ? typeName(type.args[0])?.name : undefined;
491
+
492
+ let kind: ParamKind;
493
+ let cells = 1;
494
+ if (param.isArray && param.type === 'Float' && param.arraySize === '3') {
495
+ kind = 'vector';
496
+ } else if (param.isArray && element === 'number') {
497
+ if (!isLength(pawn[i + 1])) {
498
+ problems.push(`${where} - ${param.name}[]: an array needs its size right after it`);
499
+ return null;
500
+ }
501
+ kind = param.type === 'Float' ? 'floats' : 'ints';
502
+ cells = 2;
503
+ i++;
504
+ } else if (param.isArray && tsName === 'string') {
505
+ kind = 'string';
506
+ } else if (param.isArray) {
507
+ problems.push(`${where} - parameter "${node.name.text}": ${param.name}[] is text (string) or an array (number[])`);
508
+ return null;
509
+ } else if (tsName === 'Player') {
510
+ kind = type!.nullable ? 'player?' : 'player';
511
+ } else if (tsName === 'Team') {
512
+ // A team crosses as its TeamName number - the Forward's mapping.
513
+ if (param.type !== 'TeamName') {
514
+ problems.push(`${where} - parameter "${node.name.text}": a Team is a TeamName number, and the include declares ${param.type !== 'any' ? `${param.type}:` : ''}${param.name}`);
515
+ return null;
516
+ }
517
+ kind = 'team';
518
+ } else if (param.type === 'Float') {
519
+ kind = 'float';
520
+ } else if (param.type === 'bool' || tsName === 'boolean') {
521
+ kind = 'bool';
522
+ } else if (tsName === 'number') {
523
+ kind = 'int';
524
+ } else {
525
+ kind = 'tag';
526
+ }
527
+
528
+ if (kind === 'string') lastString = reads.length;
529
+ reads.push(readOf(kind, String(at)));
530
+ params.push({ name: node.name.text, kind, pawnDefault: pawnDefault(kind, node.initializer), tag: kind === 'tag' ? tsName : undefined });
531
+ starts.push(at);
532
+ layout.push({ param: params[params.length - 1], index: next });
533
+ next++;
534
+ at += cells;
535
+ }
536
+
537
+ if (next < parameters.length) {
538
+ problems.push(`${where} - parameter "${parameters[next].name.text}": the include declares no argument for it`);
539
+ return null;
540
+ }
541
+ return { layout, params, reads, outputs, starts };
542
+ }
543
+
544
+ /**
545
+ * The names of the object a native with several outputs returns, in order -
546
+ * off the first object literal in its body. They fill the outputs in order:
547
+ * `{ name, langKey, pointer }` for `szName[], iNameLen, szLangKey[], iLangLen, &iCvarPtr`.
548
+ */
549
+ function objectFields(body: any): string[] | null {
550
+ const seen = new Set<any>();
551
+ const walk = (node: any): string[] | null => {
552
+ if (!node || typeof node !== 'object' || seen.has(node)) return null;
553
+ seen.add(node);
554
+ if (node.kind === asc.NodeKind.Literal && node.literalKind === asc.LiteralKind.Object) {
555
+ return node.names.map((n: any) => n.text);
556
+ }
557
+ for (const key of Object.keys(node)) {
558
+ if (key === 'range' || key === 'parent') continue;
559
+ const value = node[key];
560
+ if (Array.isArray(value)) {
561
+ for (const item of value) {
562
+ const found = walk(item);
563
+ if (found) return found;
564
+ }
565
+ } else if (value && typeof value === 'object' && typeof value.kind === 'number') {
566
+ const found = walk(value);
567
+ if (found) return found;
568
+ }
569
+ }
570
+ return null;
571
+ };
572
+ return walk(body);
573
+ }
574
+
575
+ /** What the wrapper of a contract native is made from. */
576
+ interface ContractWrapper {
577
+ plan: ContractPlan;
578
+ /** The result object's fields, for a native with several outputs. */
579
+ fields: string[] | null;
580
+ /** Whether the function returns anything at all. */
581
+ returnsValue: boolean;
582
+ }
583
+
584
+ /** The body of a contract native's wrapper: read, call, write back. */
585
+ function contractBody(call: string, native: PluginNative, plan: ContractPlan, fields: string[] | null, returnsValue: boolean): string {
586
+ const outputs = plan.outputs;
587
+ if (!returnsValue) return `${call};`;
588
+ if (outputs.length === 0 && native.pawnResult === 'Array') {
589
+ // An `Array:` handle: a list becomes one; an object, an Array of its fields' Arrays.
590
+ if (!fields) return `__nativeResult(__nativePawnArray(${call}));`;
591
+ const each = fields.map(field => `__nativePawnArray(__r!.${field})`).join(', ');
592
+ return [`const __r = ${call};`, 'if (changetype<usize>(__r) == 0) {', '\t\t__nativeResult(0);', '\t\treturn;', '\t}', `__nativeResult(__nativePawnHandles([${each}]));`].join('\n\t');
593
+ }
594
+ if (outputs.length === 0) {
595
+ return native.pawnResult === 'Float' ? `__nativeReturnFloat(${call});` : `__nativeResult(${call});`;
596
+ }
597
+
598
+ const single = outputs.length === 1 ? outputs[0] : null;
599
+ if (single && single.kind === 'string') return `__nativeReturn(${call}, ${single.at});`;
600
+ if (single && single.kind === 'floats') return `__nativeReturnFloats(${call}, ${single.at});`;
601
+
602
+ // Several outputs, or one through a reference: an object, or null.
603
+ const lines = [`const __r = ${call};`, 'if (changetype<usize>(__r) == 0) {', '\t\t__nativeResult(0);', '\t\treturn;', '\t}'];
604
+ outputs.forEach((output, i) => {
605
+ const field = fields![i];
606
+ const write = output.kind === 'float-ref' || output.kind === 'floats' ? '__nativeOutFloat' : '__nativeOut';
607
+ lines.push(`${write}(__r!.${field}, ${output.at});`);
608
+ });
609
+ lines.push('__nativeResult(1);');
610
+ return lines.join('\n\t');
611
+ }
612
+
613
+ /**
614
+ * What a contract native answers when a Player argument is not a player: the
615
+ * result's default - "" in a single out[] (and 0, its length), 0 otherwise,
616
+ * which Pawn reads as false, 0.0 or null (outputs left alone).
617
+ */
618
+ function contractDefault(plan: ContractPlan): string {
619
+ const single = plan.outputs.length === 1 ? plan.outputs[0] : null;
620
+ return single && single.kind === 'string' ? `__nativeReturn("", ${single.at});` : '__nativeResult(0);';
621
+ }
622
+
623
+ /** The hidden entry: one wrapper per native, each registered under its name. */
624
+ function wrappersSource(entry: string, natives: PluginNative[], declared: Map<string, ResultKind | null>, plans: Map<string, ContractWrapper> = new Map()): string {
625
+ const base = entry.replace(/\\/g, '/').split('/').pop()!.replace(/\.ts$/, '');
626
+ const helpers = new Set(['__native', '__nativeCall', '__nativeReturn', '__nativeReturnFloat', '__nativeReturnFloats', '__nativeReturnArray']);
627
+ const lines: string[] = [];
628
+
629
+ natives.forEach((native, n) => {
630
+ const contract = plans.get(native.name);
631
+ if (contract) {
632
+ for (const read of contract.plan.reads) {
633
+ for (const m of read.matchAll(/__native\w+/g)) helpers.add(m[0]);
634
+ }
635
+ const check = playerCheck(contract.plan.params, i => String(contract.plan.starts[i]));
636
+ // A native that takes a player and returns nothing still answers
637
+ // whether it ran: 1, and 0 when the id was no player - what the Pawn
638
+ // plugins' natives do (a set_collision_exception), and a caller
639
+ // that ignores the answer loses nothing.
640
+ let body = contractBody(`__n${n}(${contract.plan.reads.join(', ')})`, native, contract.plan, contract.fields, contract.returnsValue);
641
+ if (check && !contract.returnsValue) body += '\n\t__nativeResult(1);';
642
+ const guard = check ? `if (${check}) {\n\t\t${contractDefault(contract.plan)}\n\t\treturn;\n\t}\n\t` : '';
643
+ for (const m of (guard + body).matchAll(/__native\w+/g)) helpers.add(m[0]);
644
+ lines.push(`function __amxts_native${n}(): void {\n\t__nativeCall();\n\t${guard}${body}\n}\n__native("${native.name}", __amxts_native${n});`);
645
+ return;
646
+ }
647
+
648
+ const { at, out, split, cells } = indexes(native.params);
649
+ // Past the buffer's place a cell is two further on when the caller
650
+ // passed a buffer: `__s` is 2 then, and 0 otherwise.
651
+ const shifted = split < native.params.length;
652
+ if (shifted) helpers.add('__nativeShift');
653
+ const args = native.params.map((param, i) => {
654
+ helpers.add(READERS[param.kind]);
655
+ return readOf(param.kind, `${at[i]}${i >= split ? ' + __s' : ''}`);
656
+ }).join(', ');
657
+ const call = `__n${n}(${args})`;
658
+ const result = declared.get(native.name);
659
+ const check = playerCheck(native.params, i => `${at[i]}${i >= split ? ' + __s' : ''}`);
660
+ if (check) helpers.add('__nativeTarget').add('__nativeSkip');
661
+ const guard = check ? `if (${check}) {\n\t\t__nativeSkip(${out}, ${cells});\n\t\treturn;\n\t}\n\t` : '';
662
+
663
+ const body = result === 'void'
664
+ ? `${call};`
665
+ : result === 'float'
666
+ ? `__nativeReturnFloat(${call});`
667
+ : result === 'floats'
668
+ ? `__nativeReturnFloats(${call}, ${out});`
669
+ : result === 'array'
670
+ ? `__nativeReturnArray(${call});`
671
+ : `__nativeReturn(${call}, ${out});`;
672
+ const shift = shifted ? `const __s = __nativeShift(${cells});\n\t` : '';
673
+
674
+ lines.push(`function __amxts_native${n}(): void {\n\t__nativeCall();\n\t${shift}${guard}${body}\n}\n__native("${native.name}", __amxts_native${n});`);
675
+ });
676
+
677
+ return [
678
+ '// GENERATED by scripts/plugin-natives.ts: the natives this plugin exports.',
679
+ `import { ${[...helpers].join(', ')} } from "~/facade";`,
680
+ `import { ${natives.map((native, n) => `${native.name} as __n${n}`).join(', ')} } from "./${base}";`,
681
+ '',
682
+ ...lines,
683
+ '',
684
+ ].join('\n');
685
+ }
686
+
687
+ /**
688
+ * The transform for one compile. `found` is filled with the plugin's natives
689
+ * once the compile is over; an unsupported signature stops the compile with
690
+ * a message naming the function and the parameter.
691
+ */
692
+ export function nativesTransform(entry: string, found: PluginNative[], root: string = 'as'): any {
693
+ const wanted = entry.replace(/\\/g, '/').replace(/\.ts$/, '');
694
+ let source: any = null;
695
+ const declared = new Map<string, ResultKind | null>();
696
+ const enums: PluginEnum[] = [];
697
+ ENUMS.set(found, enums);
698
+
699
+ return class {
700
+ afterParse(parser: any): void {
701
+ source = parser.sources.find((s: any) =>
702
+ s.sourceKind === asc.SourceKind.UserEntry && s.normalizedPath.replace(/\.ts$/, '') === wanted) ?? null;
703
+ if (!source) return;
704
+
705
+ const problems: string[] = [];
706
+ const contract = readContract(source, entry, root, problems);
707
+ const author = pluginInfo(source).author;
708
+ if (author) AUTHORS.set(found, author);
709
+ const plans = new Map<string, ContractWrapper>();
710
+
711
+ // An exported enum is a Pawn enum: its name is the tag a parameter or
712
+ // a result typed with it carries, its members the constants.
713
+ for (const statement of source.statements) {
714
+ if (statement.kind !== asc.NodeKind.EnumDeclaration) continue;
715
+ if (!(statement.flags & asc.CommonFlags.Export)) continue;
716
+ const members: { name: string; value: number }[] = [];
717
+ let next = 0;
718
+ for (const member of statement.values) {
719
+ const text = member.initializer ? String(asc.ASTBuilder.build(member.initializer)).trim() : String(next);
720
+ if (!/^-?\d+$/.test(text)) {
721
+ problems.push(`${source.normalizedPath}: export enum ${statement.name.text} - ${member.name.text} = ${text}: a Pawn enum takes whole-number literals`);
722
+ continue;
723
+ }
724
+ members.push({ name: member.name.text, value: Number(text) });
725
+ next = Number(text) + 1;
726
+ }
727
+ enums.push({ name: statement.name.text, members, doc: docOf(source.text, statement.range.start) });
728
+ }
729
+ const tags = new Set(enums.map(e => e.name));
730
+ const tagOf = (node: any) => {
731
+ const type = typeName(node);
732
+ return type && tags.has(type.name) ? type.name : undefined;
733
+ };
734
+
735
+ for (const statement of source.statements) {
736
+ if (statement.kind !== asc.NodeKind.FunctionDeclaration) continue;
737
+ if (!(statement.flags & asc.CommonFlags.Export) || (statement.flags & asc.CommonFlags.Ambient)) continue;
738
+
739
+ const name: string = statement.name.text;
740
+ if (!isNative(name)) continue;
741
+ const where = `${source.normalizedPath}: export function ${name}`;
742
+
743
+ if (statement.typeParameters?.length) {
744
+ problems.push(`${where} - a native cannot be generic`);
745
+ continue;
746
+ }
747
+ if (name.length > 31) problems.push(`${where} - Pawn names are at most 31 characters`);
748
+
749
+ const declaration = contract?.natives.get(name);
750
+ if (declaration) {
751
+ const plan = contractPlan(declaration, statement.signature.parameters, where, problems);
752
+ if (!plan) continue;
753
+ const arrayResult = declaration.returnType === 'Array' && plan.outputs.length === 0;
754
+ const several = plan.outputs.length > 1 || plan.outputs.some(o => o.kind === 'ref' || o.kind === 'float-ref');
755
+ const fields = several || arrayResult ? objectFields(statement.body) : null;
756
+ if (several && (!fields || fields.length !== plan.outputs.length)) {
757
+ problems.push(`${where} - ${declaration.name} writes ${plan.outputs.length} arguments: return an object literal with a field for each, in order, or null`);
758
+ continue;
759
+ }
760
+ const writtenType = typeName(statement.signature.returnType)?.name ?? '';
761
+ const written = writtenType !== '' && writtenType !== 'void';
762
+ plans.set(name, { plan, fields, returnsValue: written || statement.signature.inferFrom?.length > 0 });
763
+
764
+ // The result as Pawn reads it: the declaration's tag says float
765
+ // or bool; an object is true, null false; the rest is inferred.
766
+ const pawnResult = declaration.returnType;
767
+ const result: ResultKind | null = arrayResult ? 'array' : fields ? 'bool' : pawnResult === 'Float' ? 'float' : null;
768
+ declared.set(name, result);
769
+ found.push({
770
+ name,
771
+ params: plan.params,
772
+ result: result ?? 'void',
773
+ doc: docOf(source.text, statement.range.start),
774
+ layout: plan.layout,
775
+ pawnResult,
776
+ declaration,
777
+ });
778
+ continue;
779
+ }
780
+
781
+ const params: NativeParam[] = [];
782
+ for (const parameter of statement.signature.parameters) {
783
+ const paramName: string = parameter.name.text;
784
+ const kind = isOptionalString(parameter) ? 'string' : paramKind(parameter.type, tags);
785
+ if (parameter.parameterKind === asc.ParameterKind.Rest || !kind) {
786
+ const written = parameter.type ? String(asc.ASTBuilder.build(parameter.type)) : '?';
787
+ problems.push(`${where} - parameter "${paramName}: ${written}": a native takes string, number, Float, boolean, number[], Float[], Vector, Player or an exported enum`);
788
+ continue;
789
+ }
790
+ params.push({ name: paramName, kind, pawnDefault: pawnDefault(kind, parameter.initializer), tag: tagOf(parameter.type) });
791
+ }
792
+
793
+ // A written type is the result; an omitted one is `void` unless the
794
+ // body returns a value, and then the compiler says which type.
795
+ const returnType = statement.signature.returnType;
796
+ const omitted = typeName(returnType)?.name === '';
797
+ const result = !omitted
798
+ ? resultKind(returnType, tags)
799
+ : statement.signature.inferFrom?.length
800
+ ? null
801
+ : 'void';
802
+ if (!omitted && !result) {
803
+ problems.push(`${where} - result ${String(asc.ASTBuilder.build(returnType))}: a native returns string, string | null, number, Float, boolean, number[], Float[], CellArray or an exported enum`);
804
+ }
805
+ declared.set(name, result);
806
+
807
+ const resultTag = result === 'tag' ? tagOf(returnType) : undefined;
808
+ found.push({ name, params, result: result ?? 'void', resultTag, doc: docOf(source.text, statement.range.start) });
809
+ }
810
+
811
+ // The include is a promise to the Pawn plugins compiled against it:
812
+ // every native it declares has to be there.
813
+ if (contract) {
814
+ for (const declaredName of contract.natives.keys()) {
815
+ if (!found.some(native => native.name === declaredName)) {
816
+ problems.push(`${source.normalizedPath}: ${contract.file} declares ${declaredName}, and the plugin does not export it`);
817
+ }
818
+ }
819
+ CONTRACTS.set(found, contract);
820
+ }
821
+
822
+ crossForwards(parser.sources, contract, problems);
823
+
824
+ if (problems.length) throw new Error(problems.join('\n'));
825
+ if (found.length === 0) return;
826
+
827
+ const dir = source.normalizedPath.includes('/') ? source.normalizedPath.replace(/\/[^/]*$/, '/') : '';
828
+ parser.parseFile(wrappersSource(source.normalizedPath, found, declared, plans), `${dir}${WRAPPERS}`, true);
829
+ }
830
+
831
+ afterCompile(): void {
832
+ if (!source) return;
833
+ const program = (this as any).program;
834
+ const problems: string[] = [];
835
+
836
+ for (const native of found) {
837
+ if (declared.get(native.name)) continue;
838
+ const instance = program.instancesByName.get(`${source.internalPath}/${native.name}`);
839
+ const kind = instance?.signature ? inferredKind(instance.signature.returnType) : null;
840
+ if (!kind) {
841
+ const text = instance?.signature ? String(instance.signature.returnType.toString()) : 'unknown';
842
+ problems.push(`${source.normalizedPath}: export function ${native.name} - result ${text}: a native returns string, string | null, number, Float, boolean, number[], Float[], CellArray or an exported enum`);
843
+ continue;
844
+ }
845
+ native.result = kind;
846
+ }
847
+
848
+ // Counted once every result is known: a string result adds an out-buffer.
849
+ for (const native of found) {
850
+ const count = pawnArgCount(native);
851
+ if (count > MAX_NATIVE_ARGS) {
852
+ problems.push(`${source.normalizedPath}: export function ${native.name} - Pawn passes it ${count} arguments, and a native takes at most ${MAX_NATIVE_ARGS}: pass an array or split it`);
853
+ }
854
+ }
855
+
856
+ if (problems.length) throw new Error(problems.join('\n'));
857
+ }
858
+ };
859
+ }
860
+
861
+ // A Forward's arguments, as its Pawn declaration has them.
862
+ //
863
+ // export const playerJoinedTeam = new Forward<Player, Team>("myplugin_on_player_joined_team");
864
+ // // forward myplugin_on_player_joined_team(id, TeamName:iTeam);
865
+ //
866
+ // `Team` is a string in compiled code, so without the declaration the facade
867
+ // could only send Pawn the text "CT". The build finds the forward in the
868
+ // plugin's include contract or in any include it knows, and tells the Forward
869
+ // how each argument crosses: an extra constructor argument, one letter per
870
+ // argument - `t` a team as its TeamName number, `w` a round's winner as its
871
+ // WinStatus number, `f` a number as a Float, `F` an array as Floats, `_` as
872
+ // the type says. A forward no include declares crosses as its types say, a
873
+ // `Float` and a `Float[]` as Floats. A string where Pawn has a cell, an array
874
+ // where it has a cell, a Team for a forward no include declares, and a count
875
+ // that does not match are build errors: the value would otherwise reach Pawn
876
+ // in silence.
877
+
878
+ /** Every forward the includes declare, by name; read once. */
879
+ let knownForwards: Map<string, ForwardDeclaration> | null = null;
880
+
881
+ function includeForwards(): Map<string, ForwardDeclaration> {
882
+ if (knownForwards) return knownForwards;
883
+ knownForwards = new Map();
884
+ for (const name of listIncludes()) {
885
+ let text: string;
886
+ try {
887
+ text = readInclude(name);
888
+ } catch {
889
+ continue;
890
+ }
891
+ for (const forward of new IncludeParser(text).parse().forwards) {
892
+ if (!knownForwards.has(forward.name)) knownForwards.set(forward.name, forward);
893
+ }
894
+ }
895
+ return knownForwards;
896
+ }
897
+
898
+ /** The forward as the first include that declares it has it - what decides how a Forward's arguments cross - or null. */
899
+ export function includeForward(name: string): ForwardDeclaration | null {
900
+ return includeForwards().get(name) ?? null;
901
+ }
902
+
903
+ /** A file of the plugin, as opposed to AssemblyScript's library and the hood. */
904
+ function isPluginFile(source: any): boolean {
905
+ const path: string = source.internalPath;
906
+ if (!path.startsWith('~lib/')) return true;
907
+ if (!path.startsWith('~lib/~/')) return false;
908
+ return !['facade', 'natives', 'constants', 'events', 'entities', 'hooks', 'flags'].includes(path.slice(7));
909
+ }
910
+
911
+ /** A Forward's arrays, as writtenType names them. */
912
+ const ARRAY_TYPES = new Set(['number[]', 'Float[]', 'Vector']);
913
+
914
+ /** A Forward's type argument by name: `Player`, `string`, `number[]`, `Float[]`. */
915
+ function writtenType(node: any): string {
916
+ const type = typeName(node);
917
+ if (type?.name === 'Array' && type.args.length === 1) return `${typeName(type.args[0])?.name ?? '?'}[]`;
918
+ return type?.name ?? '?';
919
+ }
920
+
921
+ /** Every `new Forward<...>(...)` under a node. */
922
+ function forwardsIn(node: any, found: any[], seen: Set<any> = new Set()): any[] {
923
+ if (!node || typeof node !== 'object' || seen.has(node)) return found;
924
+ seen.add(node);
925
+ if (Array.isArray(node)) {
926
+ for (const item of node) forwardsIn(item, found, seen);
927
+ return found;
928
+ }
929
+ if (node.kind === asc.NodeKind.New && node.typeName?.identifier?.text === 'Forward') found.push(node);
930
+ for (const key of Object.keys(node)) {
931
+ if (key === 'range' || key === 'source' || key === 'parent') continue;
932
+ const value = node[key];
933
+ if (value && typeof value === 'object') forwardsIn(value, found, seen);
934
+ }
935
+ return found;
936
+ }
937
+
938
+ function crossForwards(sources: any[], contract: Contract | null, problems: string[]): void {
939
+ const plugin = sources.filter(isPluginFile);
940
+
941
+ // A type alias for a string - `type GameMode = "normal" | ... | (string & {})` -
942
+ // is a string as it crosses, whatever its name says.
943
+ const stringAliases = new Set<string>(['string']);
944
+ for (const source of plugin) {
945
+ for (const statement of source.statements) {
946
+ if (statement.kind === asc.NodeKind.TypeDeclaration && typeName(statement.type)?.name === 'string') {
947
+ stringAliases.add(statement.name.text);
948
+ }
949
+ }
950
+ }
951
+ stringAliases.delete('Team');
952
+
953
+ const contractForwards = contract ? new IncludeParser(contract.text).parse().forwards : [];
954
+ for (const source of plugin) {
955
+ for (const node of forwardsIn(source.statements, [])) {
956
+ const written: string[] = (node.typeArguments ?? []).map(writtenType);
957
+ const first = node.args[0];
958
+ const name = first && first.kind === asc.NodeKind.Literal && first.literalKind === asc.LiteralKind.String ? first.value as string : null;
959
+ const where = `${source.normalizedPath}: new Forward<${written.join(', ')}>(${name ? `"${name}"` : '...'})`;
960
+
961
+ const declaration = name ? contractForwards.find(f => f.name === name) ?? includeForwards().get(name) : undefined;
962
+ if (!declaration) {
963
+ if (written.includes('Team') || written.includes('RoundWinner')) {
964
+ problems.push(`${where} - no include declares ${name ?? 'this forward'}, so nothing says Pawn wants a number: a Team or a RoundWinner would go as text; declare the forward in an include`);
965
+ }
966
+ // Without a declaration the types say it: a Float, and a Float[], as Floats.
967
+ const crossing = written.map(type => type === 'Float' ? 'f' : type === 'Float[]' ? 'F' : '_').join('');
968
+ if (/[^_]/.test(crossing) && node.args.length === 1) {
969
+ node.args.push(asc.Node.createStringLiteralExpression(crossing, node.range));
970
+ }
971
+ continue;
972
+ }
973
+
974
+ if (written.length !== declaration.params.length) {
975
+ problems.push(`${where} - the include declares ${declaration.name} with ${declaration.params.length} argument(s)`);
976
+ continue;
977
+ }
978
+
979
+ let crossing = '';
980
+ declaration.params.forEach((param, i) => {
981
+ const type = written[i];
982
+ // Float:pos[3] is numbers, not text.
983
+ const text = param.isArray === true && param.type !== 'Float';
984
+ const pawn = `${param.type !== 'any' ? `${param.type}:` : ''}${param.name}${param.isArray ? '[]' : ''}`;
985
+ if (ARRAY_TYPES.has(type)) {
986
+ if (!param.isArray) {
987
+ problems.push(`${where} - argument ${i + 1} is ${pawn} in the include, a cell: a ${type} would go as an array`);
988
+ } else if (type === 'Vector' && param.type !== 'Float') {
989
+ problems.push(`${where} - argument ${i + 1} is ${pawn} in the include: a Vector is three Floats, Float:${param.name}[3]`);
990
+ } else if (!param.type || param.type === 'any') {
991
+ // The host's public takes an untagged array as text (generate-host.ts, kindOf).
992
+ problems.push(`${where} - argument ${i + 1} is ${pawn} in the include, without a tag: Pawn passes it as text, and a ${type} would arrive empty; tag it, Float:${param.name}[]`);
993
+ } else {
994
+ crossing += param.type === 'Float' ? 'F' : '_';
995
+ }
996
+ } else if (type === 'Team' && !text) {
997
+ crossing += 't';
998
+ } else if (type === 'RoundWinner' && param.type === 'WinStatus') {
999
+ crossing += 'w';
1000
+ } else if (type === 'RoundWinner') {
1001
+ problems.push(`${where} - argument ${i + 1} is ${pawn} in the include: a RoundWinner crosses only as a WinStatus`);
1002
+ } else if (stringAliases.has(type) && !text) {
1003
+ problems.push(`${where} - argument ${i + 1} is ${pawn} in the include, a cell: a ${type} would go as text${param.type === 'TeamName' ? '; write Team' : '; send its number'}`);
1004
+ } else if (text && !stringAliases.has(type)) {
1005
+ problems.push(`${where} - argument ${i + 1} is ${pawn} in the include: text, not a ${type}`);
1006
+ } else if ((type === 'number' || type === 'Float') && param.type === 'Float') {
1007
+ crossing += 'f';
1008
+ } else {
1009
+ crossing += '_';
1010
+ }
1011
+ });
1012
+
1013
+ if (/[^_]/.test(crossing) && node.args.length === 1) {
1014
+ node.args.push(asc.Node.createStringLiteralExpression(crossing, node.range));
1015
+ }
1016
+ }
1017
+ }
1018
+ }
1019
+
1020
+ // Pawn's own words, which a parameter cannot be called.
1021
+ const PAWN_RESERVED = new Set([
1022
+ 'assert',
1023
+ 'break',
1024
+ 'case',
1025
+ 'char',
1026
+ 'const',
1027
+ 'continue',
1028
+ 'default',
1029
+ 'defined',
1030
+ 'do',
1031
+ 'else',
1032
+ 'enum',
1033
+ 'exit',
1034
+ 'for',
1035
+ 'forward',
1036
+ 'goto',
1037
+ 'if',
1038
+ 'native',
1039
+ 'new',
1040
+ 'operator',
1041
+ 'public',
1042
+ 'return',
1043
+ 'sizeof',
1044
+ 'sleep',
1045
+ 'state',
1046
+ 'static',
1047
+ 'stock',
1048
+ 'switch',
1049
+ 'tagof',
1050
+ 'while',
1051
+ 'decl',
1052
+ ]);
1053
+
1054
+ /** A plugin's name as a Pawn identifier: `api-natives` is `api_natives`. */
1055
+ export function includeName(plugin: string): string {
1056
+ return plugin.replace(/\W/g, '_');
1057
+ }
1058
+
1059
+ /** The `native` line of one of them, with its comment. */
1060
+ export function pawnNative(native: PluginNative): string {
1061
+ const used = new Set<string>();
1062
+ const name = (wanted: string) => {
1063
+ let n = PAWN_RESERVED.has(wanted) ? `${wanted}_` : wanted;
1064
+ while (used.has(n)) n += '_';
1065
+ used.add(n);
1066
+ return n;
1067
+ };
1068
+ // The parameters keep their own names: a named argument (`.index = 1`)
1069
+ // is written with them, so the buffer's `out` is the one to give way.
1070
+ // `player: Player` is `id` in Pawn, the name every AMX Mod X include
1071
+ // gives a player - and the one the include had before it took a Player.
1072
+ // A parameter the function ignores (`_isCritical`) is still the one Pawn
1073
+ // passes, under its own name.
1074
+ const own = native.params.map(param =>
1075
+ name(param.name === 'player' && (param.kind === 'player' || param.kind === 'player?') ? 'id' : param.name.replace(/^_\B/, '')));
1076
+
1077
+ const result = native.result;
1078
+ const params: string[] = [];
1079
+ for (const slot of pawnLayout(native)) {
1080
+ if ('out' in slot) {
1081
+ if (result === 'string' || result === 'string?') params.push(`${name('out')}[]`, name('len'));
1082
+ if (result === 'ints') params.push(`${name('out')}[]`, name('size'));
1083
+ if (result === 'floats') params.push(`Float:${name('out')}[]`, name('size'));
1084
+ continue;
1085
+ }
1086
+ if (!('param' in slot)) continue;
1087
+ const param = slot.param;
1088
+ const n = own[slot.index];
1089
+ const fallback = param.pawnDefault === null ? '' : ` = ${param.pawnDefault}`;
1090
+ if (param.kind === 'string') params.push(`const ${n}[]${fallback}`);
1091
+ else if (param.kind === 'int' || param.kind === 'player' || param.kind === 'player?') params.push(`${n}${fallback}`);
1092
+ else if (param.kind === 'tag') params.push(`${param.tag}:${n}${fallback}`);
1093
+ else if (param.kind === 'float') params.push(`Float:${n}${fallback}`);
1094
+ else if (param.kind === 'bool') params.push(`bool:${n}${fallback}`);
1095
+ else if (param.kind === 'vector') params.push(`const Float:${n}[3]`);
1096
+ else params.push(`const ${param.kind === 'floats' ? 'Float:' : ''}${n}[]`, name(`${param.name}_size`));
1097
+ }
1098
+
1099
+ const tag = result === 'float'
1100
+ ? 'Float:'
1101
+ : result === 'bool' || result === 'string?'
1102
+ ? 'bool:'
1103
+ : result === 'tag'
1104
+ ? `${native.resultTag}:`
1105
+ : result === 'array'
1106
+ ? 'Array:'
1107
+ : '';
1108
+ const line = `native ${tag}${native.name}(${params.join(', ')});`;
1109
+ return native.doc ? `${native.doc.replace(/^[ \t]+/gm, ' ').replace(/^ \/\*\*/, '/**')}\n${line}` : line;
1110
+ }
1111
+
1112
+ /** An `export enum` as Pawn writes it, with its comment. */
1113
+ export function pawnEnum(declared: PluginEnum): string {
1114
+ const members = declared.members.map(m => `\t${m.name} = ${m.value}`).join(',\n');
1115
+ const text = `enum ${declared.name} {\n${members}\n}`;
1116
+ return declared.doc ? `${declared.doc.replace(/^[ \t]+/gm, ' ').replace(/^ \/\*\*/, '/**')}\n${text}` : text;
1117
+ }
1118
+
1119
+ /** The include a Pawn plugin writes `#include <name>` for. */
1120
+ export function pawnInclude(plugin: string, natives: PluginNative[]): string {
1121
+ // A plugin that implements an include hands out that include, as it is:
1122
+ // what the Pawn plugins were compiled against. Natives it adds of its own
1123
+ // follow, declared from their TypeScript types.
1124
+ const contract = nativeContract(natives);
1125
+ if (contract) {
1126
+ const extra = natives.filter(native => !native.declaration);
1127
+ if (extra.length === 0) return contract.text;
1128
+ return `${contract.text.trimEnd()}\n\n/* Added by the TypeScript plugin that implements this include. */\n\n${extra.map(pawnNative).join('\n\n')}\n`;
1129
+ }
1130
+
1131
+ const guard = `_${includeName(plugin)}_included`;
1132
+ const enums = nativeEnums(natives);
1133
+ return [
1134
+ '/*',
1135
+ ` * ${plugin} - natives`,
1136
+ // A */ in the name would end the comment: *\/ does not.
1137
+ ...(AUTHORS.has(natives) ? [` * Author: ${AUTHORS.get(natives)!.replaceAll('*/', '*\\/')}`] : []),
1138
+ ' *',
1139
+ ` * Generated by amxts from ${plugin}.ts. Do not edit it: change the plugin`,
1140
+ ' * and build it again.',
1141
+ ' *',
1142
+ ' * A string result is written into out[], len being its size (charsmax).',
1143
+ ' * The native returns the string\'s length - or, when there may be no',
1144
+ ' * string, bool: whether there was one. An array result goes into out[],',
1145
+ ' * size being its size, and the native returns how many cells it wrote.',
1146
+ ' */',
1147
+ '',
1148
+ `#if defined ${guard}`,
1149
+ '\t#endinput',
1150
+ '#endif',
1151
+ `#define ${guard}`,
1152
+ '',
1153
+ ...enums.map(declared => `${pawnEnum(declared)}\n`),
1154
+ natives.map(pawnNative).join('\n\n'),
1155
+ '',
1156
+ ].join('\n');
1157
+ }