@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
package/as/facade.ts ADDED
@@ -0,0 +1,4639 @@
1
+ /// <reference path="./amxts.d.ts" />
2
+ // The plugin-facing API.
3
+ //
4
+ // Two layers sit under this one: as/natives.ts, which is every native in
5
+ // includes/*.inc as Pawn sees it, and the bridge natives that
6
+ // runtime/src/module.cpp registers by hand for the places where a string
7
+ // crossing as UTF-16 is worth a purpose-built thunk. Both are direct calls —
8
+ // an import whose signature wamrc knows costs 2 ns.
9
+ //
10
+ // The `// @ts-ignore: decorator` lines are AssemblyScript's own idiom, used
11
+ // throughout its standard library: an editor reading this as TypeScript would
12
+ // otherwise mark every @external as an error, because TypeScript allows
13
+ // decorators on classes only. asc is what actually reads them.
14
+ //
15
+ // What this file adds is shape: a player as an object, a target for a message,
16
+ // a handler registered rather than the name of a public passed around. Game
17
+ // state is not cached: every read asks the server, because another plugin may
18
+ // have changed it in between.
19
+ import {
20
+ get_user_armor, get_user_authid, get_user_deaths, get_user_frags, get_user_ip,
21
+ get_maxplayers, is_user_alive, is_user_bot, is_user_connected,
22
+ set_user_armor, set_user_frags,
23
+ get_member, set_member, get_user_msgid, message_begin, message_end, write_byte, write_short,
24
+ rg_give_item, rg_remove_all_items, rg_set_user_bpammo, rg_get_user_bpammo, cs_get_user_bpammo, rg_round_respawn, rg_switch_weapon,
25
+ rg_reset_maxspeed, rg_set_user_team, rg_join_team, rg_find_weapon_bpack_by_name, user_kill, get_user_flags, read_flags,
26
+ get_cvar_num, get_cvar_string, set_cvar_num, set_cvar_string, get_players, get_user_info,
27
+ LibraryExists, module_exists, give_item, strip_user_weapons, user_has_weapon, engclient_cmd,
28
+ cs_get_user_team, cs_set_user_team, cs_get_user_deaths, get_speak, set_speak, cs_set_user_bpammo, cs_set_user_deaths, ExecuteHamB,
29
+ read_argc, read_argv, set_hudmessage, show_hudmessage, create_cvar, get_cvar_pointer,
30
+ get_pcvar_float, get_pcvar_num, get_pcvar_string, set_pcvar_float, set_pcvar_num, set_pcvar_string,
31
+ hook_cvar_change, get_localinfo, register_srvcmd, register_dictionary,
32
+ set_dhudmessage, show_dhudmessage, CreateHudSyncObj, ShowSyncHudMsg, ClearSyncHud,
33
+ precache_model, precache_sound, precache_generic, query_client_cvar, register_touch,
34
+ register_message, get_msg_args, get_msg_argtype, get_msg_arg_int, get_msg_arg_float, get_msg_arg_string,
35
+ set_msg_arg_int, set_msg_arg_float, set_msg_arg_string
36
+ } from "./natives";
37
+ // Promise, async/await and AbortSignal, as the globals they are in JavaScript.
38
+ import "./promise";
39
+ import { Vector } from "./vector";
40
+
41
+ // @ts-ignore: decorator
42
+ @external("env", "print_client") declare function _printClient(id: i32, channel: i32, msg: string): void;
43
+ // @ts-ignore: decorator
44
+ @external("env", "say_text") declare function _sayText(id: i32, text: string, swapTo: string): void;
45
+ // @ts-ignore: decorator
46
+ @external("env", "get_name") declare function _getName(id: i32, out: i32, max: i32): i32;
47
+ // @ts-ignore: decorator
48
+ @external("env", "get_health") declare function _getHealth(id: i32): i32;
49
+ // @ts-ignore: decorator
50
+ @external("env", "set_health") declare function _setHealth(id: i32, hp: i32): void;
51
+ // @ts-ignore: decorator
52
+ @external("env", "clcmd") declare function _clcmd(pattern: string, fn: i32, flags: i32, info: string, shape: i32): i32;
53
+ // @ts-ignore: decorator
54
+ @external("env", "task") declare function _task(secondsBits: i32, fn: i32, id: i32, repeat: i32): i32;
55
+ // @ts-ignore: decorator
56
+ @external("env", "stop_task") declare function _stopTask(id: i32): i32;
57
+ // @ts-ignore: decorator
58
+ @external("env", "outcome") declare function _outcome(value: i32): void;
59
+ // @ts-ignore: decorator
60
+ @external("env", "export") declare function _export(name: string, fn: i32): i32;
61
+ // @ts-ignore: decorator
62
+ @external("env", "slot") declare function _slot(fn: i32, shape: i32, key: string, fallback: i32): i32;
63
+ // @ts-ignore: decorator
64
+ @external("env", "arg") declare function _arg(index: i32): i32;
65
+ // @ts-ignore: decorator
66
+ @external("env", "arg_text") declare function _argText(index: i32, out: i32, max: i32): i32;
67
+ // @ts-ignore: decorator
68
+ @external("env", "set_arg_text") declare function _setArgText(index: i32, text: string, max: i32): i32;
69
+ // @ts-ignore: decorator
70
+ @external("env", "set_arg") declare function _setArg(index: i32, value: i32): i32;
71
+ // @ts-ignore: decorator
72
+ @external("env", "caller") declare function _caller(): i32;
73
+ // @ts-ignore: decorator
74
+ @external("env", "argc") declare function _argc(): i32;
75
+ // @ts-ignore: decorator
76
+ @external("env", "plugin") declare function _plugin(name: string, version: string, author: string, description: string): void;
77
+
78
+ // call_indirect checks the type on the module's side, so a handler's shape is
79
+ // fixed at registration and there are exactly two: this one and WideHandler.
80
+ /** A handler that takes a player `id` and returns nothing — what player events and commands call. */
81
+ export type Handler = (id: number) => void;
82
+
83
+ /**
84
+ * A handler that takes up to four numbers and returns nothing: for a forward
85
+ * that passes more than a player `id`, and for a hookchain.
86
+ *
87
+ * A string argument arrives as a number: read it with `argString`. To stop
88
+ * the event, or to block what a hookchain hooks, call `handled()`.
89
+ */
90
+ export type WideHandler = (a: number, b: number, c: number, d: number) => void;
91
+
92
+ const SHAPE_NARROW: i32 = 0;
93
+ const SHAPE_WIDE: i32 = 1;
94
+
95
+ // @ts-ignore: decorator
96
+ @external("env", "tag") declare function _tag(tag: i32): void;
97
+ // @ts-ignore: decorator
98
+ @external("env", "co_id") declare function _uniqueId(): i32;
99
+
100
+ // A function handed to the host is called by its table index. A closure is
101
+ // its table entry and its variables together, so for a closure the host gets
102
+ // the dispatcher of the shape instead, with a tag - the closure's place here
103
+ // - which the host passes first (Handler.tag in runtime/src/module.cpp). A
104
+ // function declared by name goes as itself, as it always has.
105
+ const hostClosures: Object[] = [];
106
+
107
+ function narrowFired(tag: i32, id: i32): void {
108
+ changetype<Handler>(hostClosures[tag - 1])(<f64>id);
109
+ }
110
+
111
+ function wideFired(tag: i32, a: i32, b: i32, c: i32, d: i32): void {
112
+ changetype<WideHandler>(hostClosures[tag - 1])(<f64>a, <f64>b, <f64>c, <f64>d);
113
+ }
114
+
115
+ /** @hidden For the hood: what the server calls for `handler`. */
116
+ export function hostIndex<T>(handler: T, wide: bool): i32 {
117
+ const fn = changetype<usize>(handler);
118
+ if (load<usize>(fn, sizeof<u32>()) == 0) return load<u32>(fn);
119
+ hostClosures.push(changetype<Object>(fn));
120
+ _tag(hostClosures.length);
121
+ return wide ? wideFired.index : narrowFired.index;
122
+ }
123
+
124
+ // Said rather than returned, so that the nine handlers in ten which have
125
+ // nothing to say about it end without a line about this bridge.
126
+ /**
127
+ * Stops the event: AMX Mod X passes it to no one else, and a hookchain does
128
+ * not call what it hooked. Applies only to the handler that is running.
129
+ *
130
+ * function onSay(id: number) {
131
+ * if (muted(id)) handled();
132
+ * }
133
+ *
134
+ * Pawn: `return PLUGIN_HANDLED`
135
+ */
136
+ export function handled(): void {
137
+ _outcome(1);
138
+ }
139
+
140
+ /** @hidden Sets the handler's answer to a number other than `0` or `1`: a game event's status. */
141
+ export function __outcome(value: number): void {
142
+ _outcome(<i32>value);
143
+ }
144
+
145
+ // Every argument and return crosses as a 32-bit cell, and a float rides in one
146
+ // as its bit pattern. A number here is 64-bit, so the narrowing is done in one
147
+ // place rather than at every call.
148
+ /**
149
+ * Converts a number to a Pawn `Float:` cell, for a raw native or `ret()`:
150
+ *
151
+ * ret(floatCell(2.5));
152
+ * rg_round_end(floatCell(5.0), ...);
153
+ */
154
+ export function floatCell(value: f64): number {
155
+ return reinterpret<i32>(<f32>value);
156
+ }
157
+
158
+ // `<i32>x` truncates, which loses a second off every round time that is not exact.
159
+ /**
160
+ * Rounds a number to the nearest whole number: a round time of 59.6 seconds
161
+ * is `60`, not `59`.
162
+ *
163
+ * Pawn: `floatround`
164
+ */
165
+ export function rounded(value: number): number {
166
+ return <i32>Math.round(value);
167
+ }
168
+
169
+ /** Converts a Pawn `Float:` cell back to a number. */
170
+ export function cellFloat(cell: number): f64 {
171
+ return <f64>reinterpret<f32>(cell);
172
+ }
173
+
174
+ /** Sets the value an exported native returns to the plugin that called it. */
175
+ export function ret(value: number): void {
176
+ _outcome(value);
177
+ }
178
+
179
+ /**
180
+ * A public name that calls `handler`, for an AMX Mod X native that takes a
181
+ * callback by name: `register_think`, `set_native_filter`.
182
+ *
183
+ * ```ts
184
+ * const pub = publicFor(onThink, "think:myplugin_box");
185
+ * if (pub.length > 0) register_think("myplugin_box", pub);
186
+ * ```
187
+ *
188
+ * An empty name means “already registered”: such a registration cannot be
189
+ * undone and survives a hot reload, so registering again would call the
190
+ * handler twice. `key` identifies the registration across reloads — any name
191
+ * unique within the plugin. `fallback` is the answer when the handler returns
192
+ * nothing: `0` for most natives, `1` where the native expects the event handled.
193
+ *
194
+ * Register from the `"cfg"` event, not at the top of the file: a console
195
+ * command registered that early (`register_concmd`, `register_srvcmd`) crashes
196
+ * the server when typed.
197
+ */
198
+ export function publicFor(handler: WideHandler, key: string, fallback: number = 0): string {
199
+ // Must match SLOT_REUSED in runtime/src/module.cpp.
200
+ const REUSED: i32 = 0x10000;
201
+
202
+ const slot = _slot(hostIndex(handler, true), SHAPE_WIDE, key, fallback);
203
+
204
+ if (slot < 0) {
205
+ console.error(`no callback slot left for "${key}"`);
206
+ return "";
207
+ }
208
+
209
+ return (slot & REUSED) ? "" : `__amxts_cb${slot}`;
210
+ }
211
+
212
+ // AMX Mod X implements a native as a public in some plugin, so this names the
213
+ // host plugin's one public for them all, and the module keeps the name:
214
+ // register_native cannot be undone, so a reload takes the same entry back
215
+ // rather than registering the name twice. Natives are asked for before any
216
+ // plugin starts, which is why plugins are loaded from plugin_natives at all.
217
+ /**
218
+ * Exports a native for other plugins - Pawn ones included - to call.
219
+ *
220
+ * nativeFn("myplugin_get_mode", getGameMode)
221
+ *
222
+ * function getGameMode(a: number, b: number, c: number, d: number) {
223
+ * ret(mode);
224
+ * }
225
+ *
226
+ * The handler gets the first four arguments as numbers; `arg(i)` and
227
+ * `argText(i)` read any of them, `setArg` and `setArgText` write back.
228
+ * Simpler: an `export function` of the entry file is a native with real
229
+ * types (see the Natives page).
230
+ *
231
+ * Call it at the top level of the file: AMX Mod X asks every plugin for its
232
+ * natives before it starts any of them.
233
+ *
234
+ * Pawn: `register_native`
235
+ */
236
+ export function nativeFn(name: string, handler: WideHandler): void {
237
+ if (_export(name, hostIndex(handler, true)) < 0) {
238
+ console.error(`could not export the native "${name}"`);
239
+ }
240
+ }
241
+
242
+ /**
243
+ * A number that a plugin's own native passes to Pawn as `Float:`.
244
+ *
245
+ * To TypeScript it is `number`. Write it only in the signature of an exported
246
+ * native, where Pawn has to know which numbers are fractional; everywhere
247
+ * else a number is `number`.
248
+ *
249
+ * ```ts
250
+ * export function cfg_get_float(file: string, key: string): Float { ... }
251
+ * // native Float:cfg_get_float(const file[], const key[]);
252
+ * export function set_speed(id: number, speed: Float) { ... }
253
+ * // native set_speed(id, Float:speed);
254
+ * ```
255
+ */
256
+ export type Float = number;
257
+
258
+ // The hood under `export function` in a plugin's entry file. scripts/plugin-natives.ts
259
+ // writes one wrapper per exported function: it reads the Pawn caller's
260
+ // arguments with these, calls the function, and hands its result back. A
261
+ // plugin never calls them itself.
262
+
263
+ // @ts-ignore: decorator
264
+ @external("env", "arg_string") declare function _argString(index: i32, out: usize, max: i32): i32;
265
+ // @ts-ignore: decorator
266
+ @external("env", "arg_array") declare function _argArray(index: i32, out: usize, count: i32): i32;
267
+ // @ts-ignore: decorator
268
+ @external("env", "set_arg_array") declare function _setArgArray(index: i32, cells: usize, count: i32): i32;
269
+
270
+ /** @hidden Registers a generated wrapper as the native `name`. */
271
+ export function __native(name: string, wrapper: () => void): void {
272
+ if (_export(name, wrapper.index) < 0) console.error(`could not export the native "${name}"`);
273
+ }
274
+
275
+ /** @hidden A cell argument as a number. */
276
+ export function __nativeInt(index: i32): f64 {
277
+ return <f64>_arg(index);
278
+ }
279
+
280
+ /**
281
+ * A `Float:` a Pawn plugin passed, as the number it wrote: 0.1, not the
282
+ * 0.10000000149011612 its 32 bits hold. The shortest decimal that is the same
283
+ * float is what the Pawn source said - and what a value written back to a
284
+ * file, a chat line or a config should say.
285
+ */
286
+ function pawnFloat(cell: i32): f64 {
287
+ return parseFloat(reinterpret<f32>(cell).toString());
288
+ }
289
+
290
+ /** @hidden A `Float:` argument. */
291
+ export function __nativeFloat(index: i32): f64 {
292
+ return pawnFloat(_arg(index));
293
+ }
294
+
295
+ /** @hidden A `bool:` argument. */
296
+ export function __nativeBool(index: i32): bool {
297
+ return _arg(index) != 0;
298
+ }
299
+
300
+ /**
301
+ * @hidden A string argument, whole: the module says how long it is first, so
302
+ * there is no buffer to outgrow.
303
+ */
304
+ export function __nativeString(index: i32): string {
305
+ const length = _argString(index, 0, 0);
306
+ if (length <= 0) return "";
307
+ const bytes = new ArrayBuffer(length);
308
+ _argString(index, changetype<usize>(bytes), length);
309
+ return String.UTF8.decode(bytes);
310
+ }
311
+
312
+ function nativeCells(index: i32, count: i32): StaticArray<i32> {
313
+ const cells = new StaticArray<i32>(count > 0 ? count : 0);
314
+ if (count > 0) _argArray(index, changetype<usize>(cells), count);
315
+ return cells;
316
+ }
317
+
318
+ /** Cells as numbers: whole ones, or `Float:` ones read as the number they are. */
319
+ function numbersOf(cells: StaticArray<i32>, floats: bool): f64[] {
320
+ const values = new Array<f64>(cells.length);
321
+ for (let i = 0; i < cells.length; i++) unchecked(values[i] = floats ? pawnFloat(unchecked(cells[i])) : <f64>unchecked(cells[i]));
322
+ return values;
323
+ }
324
+
325
+ /**
326
+ * Numbers into the caller's `x[], size` at `index`, as many as it has room
327
+ * for: whole ones, or `Float:` ones. Returns how many.
328
+ */
329
+ function writeNumbers(values: f64[], index: i32, floats: bool): i32 {
330
+ const count = min(values.length, _arg(index + 1));
331
+ const cells = new StaticArray<i32>(count > 0 ? count : 0);
332
+ for (let i = 0; i < count; i++) unchecked(cells[i] = floats ? reinterpret<i32>(<f32>unchecked(values[i])) : <i32>unchecked(values[i]));
333
+ _setArgArray(index, changetype<usize>(cells), count);
334
+ return count;
335
+ }
336
+
337
+ /** @hidden An array argument whose size is the argument after it. */
338
+ export function __nativeInts(index: i32): f64[] {
339
+ return numbersOf(nativeCells(index, _arg(index + 1)), false);
340
+ }
341
+
342
+ /** @hidden A `Float:` array argument whose size is the argument after it. */
343
+ export function __nativeFloats(index: i32): f64[] {
344
+ return numbersOf(nativeCells(index, _arg(index + 1)), true);
345
+ }
346
+
347
+ /** @hidden A `Float:v[3]` argument. */
348
+ export function __nativeVector(index: i32): Vector {
349
+ const cells = nativeCells(index, 3);
350
+ return new Vector(pawnFloat(cells[0]), pawnFloat(cells[1]), pawnFloat(cells[2]));
351
+ }
352
+
353
+ // @ts-ignore: decorator
354
+ @external("env", "arg_length") declare function _argLength(index: i32): i32;
355
+
356
+ /**
357
+ * @hidden A forward's array argument, as many numbers as it has: what the
358
+ * emitting plugin sent, or the size the include declares; none when neither
359
+ * says. `floats` reads them as `Float:`.
360
+ */
361
+ export function __forwardNumbers(index: i32, floats: bool): f64[] {
362
+ return numbersOf(nativeCells(index, _argLength(index)), floats);
363
+ }
364
+
365
+ /**
366
+ * @hidden What the function returned, handed back the way its type says:
367
+ * a string into the caller's `out[], len`, a `string | null` the same and
368
+ * `true` or `false` as the result, an array into `out[], max` with the count
369
+ * as the result, a number or a boolean as the result itself.
370
+ */
371
+ export function __nativeReturn<T>(value: T, out: i32): void {
372
+ if (isString<T>()) {
373
+ const max = _arg(out + 1);
374
+ if (isNullable<T>()) {
375
+ if (changetype<usize>(value) == 0) {
376
+ _setArgText(out, "", max);
377
+ _outcome(0);
378
+ return;
379
+ }
380
+ _setArgText(out, changetype<string>(value), max);
381
+ _outcome(1);
382
+ return;
383
+ }
384
+ _outcome(_setArgText(out, changetype<string>(value), max));
385
+ return;
386
+ }
387
+ if (isArray<T>()) {
388
+ // @ts-ignore: valueof is AssemblyScript's
389
+ if (isBoolean<valueof<T>>() || !isFloat<valueof<T>>()) ERROR("an exported native returns number[] or Float[]");
390
+ _outcome(writeNumbers(changetype<f64[]>(value), out, false));
391
+ return;
392
+ }
393
+ if (isBoolean<T>()) {
394
+ _outcome(value ? 1 : 0);
395
+ return;
396
+ }
397
+ if (isFloat<T>() || isInteger<T>()) {
398
+ _outcome(<i32>value);
399
+ return;
400
+ }
401
+ // @ts-ignore: nonnull is AssemblyScript's
402
+ if (isReference<T>() && idof<nonnull<T>>() == idof<CellArray>()) {
403
+ __nativeReturnArray(changetype<CellArray | null>(value));
404
+ return;
405
+ }
406
+ ERROR("an exported native returns string, string | null, number, Float, boolean, number[], Float[], CellArray or an exported enum");
407
+ }
408
+
409
+ /** @hidden A cell argument under one of the plugin's exported enums. */
410
+ export function __nativeCell(index: i32): i32 {
411
+ return _arg(index);
412
+ }
413
+
414
+ /**
415
+ * @hidden Whether a `Player` argument is a player slot, 1 to maxPlayers -
416
+ * or 0, for a `Player | null` one. The wrapper calls the function only then.
417
+ */
418
+ export function __nativeTarget(index: i32, orNone: bool): bool {
419
+ const id = _arg(index);
420
+ return (orNone && id == 0) || (id >= 1 && id <= get_maxplayers());
421
+ }
422
+
423
+ /** @hidden A `Player` argument: the player in that slot, null for 0. */
424
+ export function __nativePlayer(index: i32): Player | null {
425
+ const id = _arg(index);
426
+ return id >= 1 ? new Player(id) : null;
427
+ }
428
+
429
+ /**
430
+ * @hidden Whether a `Team` argument - `TeamName:` in the include - is a
431
+ * TeamName number, 0 to 3. The wrapper calls the function only then.
432
+ */
433
+ export function __nativeIsTeam(index: i32): bool {
434
+ const team = _arg(index);
435
+ return team >= 0 && team < TEAM_NAMES.length;
436
+ }
437
+
438
+ /** @hidden A `Team` argument: its TeamName number as the name. */
439
+ export function __nativeTeam(index: i32): Team {
440
+ return TEAM_NAMES[_arg(index)];
441
+ }
442
+
443
+ /**
444
+ * @hidden A native not called because a `Player` argument was no player:
445
+ * its result's default - 0, which Pawn reads as false, 0.0 or no array, and
446
+ * "" in the caller's out[] when it passed one (`cells` is how many it passes
447
+ * without).
448
+ */
449
+ export function __nativeSkip(out: i32, cells: i32): void {
450
+ if (_argc() > cells) _setArgText(out, "", _arg(out + 1));
451
+ _outcome(0);
452
+ }
453
+
454
+ /**
455
+ * @hidden How far past its own place a parameter after the result buffer
456
+ * is: 2 when the caller passed `out[], len`, 0 when it passed only the
457
+ * parameters' `cells`. See indexes() in scripts/plugin-natives.ts.
458
+ */
459
+ export function __nativeShift(cells: i32): i32 {
460
+ return _argc() > cells ? 2 : 0;
461
+ }
462
+
463
+ /** @hidden A CellArray result: its handle, Invalid_Array (0) for null. */
464
+ export function __nativeReturnArray(value: CellArray | null): void {
465
+ _outcome(value != null ? value.handle : 0);
466
+ }
467
+
468
+ /** @hidden A `Float:` result. */
469
+ export function __nativeReturnFloat(value: f64): void {
470
+ _outcome(reinterpret<i32>(<f32>value));
471
+ }
472
+
473
+ /** @hidden How many cells the Pawn caller passed. */
474
+ export function __nativeCount(): i32 {
475
+ return _argc();
476
+ }
477
+
478
+ /** @hidden A number an `any:...` tail passes, which Pawn does by address. */
479
+ export function __nativeRef(index: i32): f64 {
480
+ return <f64>nativeCells(index, 1)[0];
481
+ }
482
+
483
+ /**
484
+ * @hidden The result of a native whose include declares no out-argument: a
485
+ * number or a boolean as the cell the native returns.
486
+ */
487
+ export function __nativeResult<T>(value: T): void {
488
+ if (isBoolean<T>()) {
489
+ _outcome(value ? 1 : 0);
490
+ return;
491
+ }
492
+ if (isFloat<T>() || isInteger<T>()) {
493
+ _outcome(<i32>value);
494
+ return;
495
+ }
496
+ ERROR("a native whose include declares no out-argument returns a number or a boolean");
497
+ }
498
+
499
+ /**
500
+ * @hidden One field of a native's result, written through the argument the
501
+ * include declares for it: text into `x[], len`, a number through `&x`, an
502
+ * array into `x[], size`.
503
+ */
504
+ export function __nativeOut<T>(value: T, index: i32): void {
505
+ if (isString<T>()) {
506
+ _setArgText(index, changetype<string>(value), _arg(index + 1));
507
+ return;
508
+ }
509
+ if (isArray<T>()) {
510
+ writeNumbers(changetype<f64[]>(value), index, false);
511
+ return;
512
+ }
513
+ if (isBoolean<T>()) {
514
+ _setArg(index, value ? 1 : 0);
515
+ return;
516
+ }
517
+ if (isFloat<T>() || isInteger<T>()) {
518
+ _setArg(index, <i32>value);
519
+ return;
520
+ }
521
+ ERROR("a native's out-argument takes a string, a number, a boolean or number[]");
522
+ }
523
+
524
+ /** @hidden The same for a `Float:&x` or `Float:x[], size` out-argument. */
525
+ export function __nativeOutFloat<T>(value: T, index: i32): void {
526
+ if (isArray<T>()) {
527
+ writeNumbers(changetype<f64[]>(value), index, true);
528
+ return;
529
+ }
530
+ _setArg(index, reinterpret<i32>(<f32><f64>value));
531
+ }
532
+
533
+ /**
534
+ * @hidden A `const fmt[], any:...` pair, formatted as AMX Mod X's format()
535
+ * would: %s %d %i %u %c %x %f (with width, `-`, `0` and a precision) and %L,
536
+ * which takes a player and a key and formats that player's translation with
537
+ * the arguments after them. The tail is passed by address, as Pawn passes
538
+ * `any:...`: a string is read there, a number through it.
539
+ */
540
+ export function __nativeFormat(index: i32): string {
541
+ return formatPawn(__nativeString(index), new FormatCursor(index + 1));
542
+ }
543
+
544
+ // Where format()'s arguments come from: the `any:...` tail of a native's
545
+ // call, read by address as Pawn passes it...
546
+ class FormatCursor {
547
+ constructor(public next: i32) {}
548
+
549
+ has(): bool {
550
+ return true;
551
+ }
552
+
553
+ text(): string {
554
+ return __nativeString(this.next++);
555
+ }
556
+
557
+ cell(): i32 {
558
+ return <i32>__nativeRef(this.next++);
559
+ }
560
+
561
+ float(): f64 {
562
+ return <f64>reinterpret<f32>(this.cell());
563
+ }
564
+ }
565
+
566
+ // ...or a list of texts, as lang.translate takes them: a number is read out
567
+ // of its text.
568
+ class TextCursor extends FormatCursor {
569
+ constructor(public args: string[]) {
570
+ super(0);
571
+ }
572
+
573
+ has(): bool {
574
+ return this.next < this.args.length;
575
+ }
576
+
577
+ text(): string {
578
+ const at = this.next++;
579
+ return at < this.args.length ? unchecked(this.args[at]) : "";
580
+ }
581
+
582
+ cell(): i32 {
583
+ const value = this.float();
584
+ return isFinite(value) ? <i32>value : 0;
585
+ }
586
+
587
+ float(): f64 {
588
+ return parseFloat(this.text());
589
+ }
590
+ }
591
+
592
+ function formatPawn(template: string, cursor: FormatCursor): string {
593
+ let out = "";
594
+ let i = 0;
595
+ const length = template.length;
596
+ while (i < length) {
597
+ const c = template.charCodeAt(i);
598
+ if (c != 0x25 || i + 1 >= length) {
599
+ out += template.charAt(i);
600
+ i++;
601
+ continue;
602
+ }
603
+
604
+ let j = i + 1;
605
+ let left = false;
606
+ let zero = false;
607
+ for (; j < length; j++) {
608
+ const flag = template.charCodeAt(j);
609
+ if (flag == 0x2D) left = true;
610
+ else if (flag == 0x30) zero = true;
611
+ else break;
612
+ }
613
+ let width = 0;
614
+ while (j < length && isDigitCode(template.charCodeAt(j))) width = width * 10 + template.charCodeAt(j++) - 0x30;
615
+ let precision = -1;
616
+ if (j < length && template.charCodeAt(j) == 0x2E) {
617
+ precision = 0;
618
+ j++;
619
+ while (j < length && isDigitCode(template.charCodeAt(j))) precision = precision * 10 + template.charCodeAt(j++) - 0x30;
620
+ }
621
+ if (j >= length) {
622
+ out += template.substring(i);
623
+ break;
624
+ }
625
+
626
+ const type = template.charAt(j);
627
+ let piece = "";
628
+ let numeric = false;
629
+ // Not a placeholder, or one whose argument the call did not pass: it stays as written.
630
+ const written = !"%sdiucxXfL".includes(type) || (type != "%" && !cursor.has());
631
+ if (written) piece = template.substring(i, j + 1);
632
+ else if (type == "%") piece = "%";
633
+ else if (type == "s") {
634
+ piece = cursor.text();
635
+ if (precision >= 0 && piece.length > precision) piece = piece.substring(0, precision);
636
+ } else if (type == "d" || type == "i" || type == "u") {
637
+ piece = cursor.cell().toString();
638
+ numeric = true;
639
+ } else if (type == "c") piece = String.fromCharCode(cursor.cell());
640
+ else if (type == "x" || type == "X") {
641
+ piece = (<u32>cursor.cell()).toString(16);
642
+ if (type == "X") piece = piece.toUpperCase();
643
+ numeric = true;
644
+ } else if (type == "f") {
645
+ piece = fixedFloat(cursor.float(), precision < 0 ? 6 : precision);
646
+ numeric = true;
647
+ } else if (type == "L") {
648
+ const player = cursor.cell();
649
+ const key = cursor.text();
650
+ const found = lookupLang(key, player > 0 ? player : 0);
651
+ piece = formatPawn(found.length > 0 ? found : `ML_NOTFOUND: ${key}`, cursor);
652
+ }
653
+
654
+ while (!written && piece.length < width) piece = left ? piece + " " : zero && numeric ? "0" + piece : " " + piece;
655
+ out += piece;
656
+ i = j + 1;
657
+ }
658
+ return out;
659
+ }
660
+
661
+ // @ts-ignore: decorator
662
+ @external("env", "LookupLangKey") declare function _lookupLangKey(out: i32, size: i32, key: i32, id: i32): i32;
663
+
664
+ /** The longest line a dictionary holds: AMX Mod X reads a value into 512 bytes. */
665
+ const LANG_TEXT: i32 = 512;
666
+
667
+ // One buffer for every lookup: a menu translates each of its lines on every draw.
668
+ const langOut = new StaticArray<i32>(LANG_TEXT);
669
+ const langId = new StaticArray<i32>(1);
670
+
671
+ /** The key's line in the player's language (`0`: the server's), as the dictionary loaded it; empty when no dictionary has it. */
672
+ function lookupLang(key: string, player: i32): string {
673
+ unchecked(langId[0] = player);
674
+ unchecked(langOut[0] = 0);
675
+ _lookupLangKey(changetype<i32>(langOut), LANG_TEXT - 1, changetype<i32>(__cellsOf(key)), changetype<i32>(langId));
676
+ return __cellText(changetype<usize>(langOut), LANG_TEXT);
677
+ }
678
+
679
+ function isDigitCode(code: i32): bool {
680
+ return code >= 0x30 && code <= 0x39;
681
+ }
682
+
683
+ /** `%.Nf`: the number with exactly `digits` digits after the point, rounded. */
684
+ function fixedFloat(value: f64, digits: i32): string {
685
+ const text = value.toFixed(Math.min(digits, 100));
686
+ // A minus the rounding left nothing of is not written: -0.001 is "0.00".
687
+ return text.startsWith("-") && parseFloat(text) == 0 ? text.substring(1) : text;
688
+ }
689
+
690
+ /**
691
+ * @hidden A value as a new AMX Mod X `Array:` - the handle, or
692
+ * Invalid_Array (0) for null. Text is an item a string; a number a Float
693
+ * cell (a TypeScript number is a float); a list of lists an Array of their
694
+ * handles, except rows of numbers, which are items of whole-number cells, as
695
+ * a list-menu row is. The Pawn plugin that gets it destroys it.
696
+ */
697
+ export function __nativePawnArray<T>(value: T): i32 {
698
+ if (isNullable<T>() && changetype<usize>(value) == 0) return 0;
699
+ if (!isArray<T>()) ERROR("an Array: result is a list");
700
+ // @ts-ignore: valueof is AssemblyScript's
701
+ if (isString<valueof<T>>()) return CellArray.fromStrings(changetype<string[]>(value)).handle;
702
+ // @ts-ignore: valueof is AssemblyScript's
703
+ if (isFloat<valueof<T>>()) return CellArray.fromFloats(changetype<f64[]>(value)).handle;
704
+ // @ts-ignore: valueof is AssemblyScript's
705
+ if (isString<valueof<valueof<T>>>()) {
706
+ const lists = changetype<string[][]>(value);
707
+ const handles = new Array<i32>(lists.length);
708
+ for (let i = 0; i < lists.length; i++) handles[i] = CellArray.fromStrings(lists[i]).handle;
709
+ return __nativePawnHandles(handles);
710
+ }
711
+ const rows = changetype<f64[][]>(value);
712
+ const array = new CellArray(rows.length > 0 ? rows[0].length : 1);
713
+ for (let i = 0; i < rows.length; i++) array.pushCells(rows[i]);
714
+ return array.handle;
715
+ }
716
+
717
+ /** @hidden An `Array:` of handles, a cell each. */
718
+ export function __nativePawnHandles(handles: i32[]): i32 {
719
+ const array = new CellArray(1);
720
+ for (let i = 0; i < handles.length; i++) array.pushCell(handles[i]);
721
+ return array.handle;
722
+ }
723
+
724
+ /** @hidden A `Float:out[], max` result; the count is what the native returns. */
725
+ export function __nativeReturnFloats(values: f64[], out: i32): void {
726
+ _outcome(writeNumbers(values, out, true));
727
+ }
728
+
729
+ // @ts-ignore: decorator
730
+ @external("env", "ArrayCreate") declare function _arrayCreate(cellSize: i32, reserved: i32): i32;
731
+ // @ts-ignore: decorator
732
+ @external("env", "ArrayPushString") declare function _arrayPushString(handle: i32, text: usize): i32;
733
+ // @ts-ignore: decorator
734
+ @external("env", "ArrayPushCell") declare function _arrayPushCell(handle: i32, value: i32): i32;
735
+ // @ts-ignore: decorator
736
+ @external("env", "ArrayPushArray") declare function _arrayPushArray(handle: i32, cells: usize, size: i32): i32;
737
+ // @ts-ignore: decorator
738
+ @external("env", "ArraySize") declare function _arraySize(handle: i32): i32;
739
+
740
+ /**
741
+ * A dynamic array of AMX Mod X, for returning a list to a Pawn plugin that
742
+ * expects an `Array:` handle.
743
+ *
744
+ * ```ts
745
+ * export function cfg_get_value_array(section: ConfigSection, key: string) {
746
+ * const words = lookup(section, key); // string[] | null
747
+ * return words != null ? CellArray.fromStrings(words) : null;
748
+ * }
749
+ * // native Array:cfg_get_value_array(ConfigSection:section, const key[]);
750
+ * ```
751
+ *
752
+ * The Pawn plugin that gets the array owns it and destroys it; `null` reaches
753
+ * it as `Invalid_Array`. Inside TypeScript a list is a `string[]` or a
754
+ * `number[]`: this class is only for handing one to Pawn.
755
+ *
756
+ * Pawn: `ArrayCreate`, `Array:`
757
+ */
758
+ export class CellArray {
759
+ /** The array's handle, the `Array:` Pawn gets. */
760
+ readonly handle: i32;
761
+
762
+ /** An empty array whose items are `cellSize` cells each: 1 for a number, the buffer size for text. */
763
+ constructor(cellSize: number = 1) {
764
+ this.handle = _arrayCreate(<i32>cellSize, 32);
765
+ }
766
+
767
+ /** A new array of strings, each up to `cellSize - 1` bytes. */
768
+ static fromStrings(values: string[], cellSize: number = 512): CellArray {
769
+ const array = new CellArray(cellSize);
770
+ for (let i = 0; i < values.length; i++) array.pushString(values[i]);
771
+ return array;
772
+ }
773
+
774
+ /** A new array of fractional numbers, which Pawn reads as `Float:`. */
775
+ static fromFloats(values: number[]): CellArray {
776
+ const array = new CellArray(1);
777
+ for (let i = 0; i < values.length; i++) array.pushFloat(values[i]);
778
+ return array;
779
+ }
780
+
781
+ /** The number of items in the array. */
782
+ get length(): number {
783
+ return _arraySize(this.handle);
784
+ }
785
+
786
+ /** Adds a string; it must fit in `cellSize - 1` bytes of UTF-8. */
787
+ pushString(value: string): void {
788
+ _arrayPushString(this.handle, changetype<usize>(__cellsOf(value)));
789
+ }
790
+
791
+ /** Adds a whole number, or another array's handle. */
792
+ pushCell(value: number): void {
793
+ _arrayPushCell(this.handle, <i32>value);
794
+ }
795
+
796
+ /** Adds a fractional number, which Pawn reads as `Float:`. */
797
+ pushFloat(value: number): void {
798
+ _arrayPushCell(this.handle, reinterpret<i32>(<f32>value));
799
+ }
800
+
801
+ /** Adds one item of several numbers: `[a, b]` into an array made with `new CellArray(2)`. */
802
+ pushCells(values: number[]): void {
803
+ pushCellArrayRow(this.handle, values);
804
+ }
805
+ }
806
+
807
+ // A handler takes four cells because the module calls it through one fixed type.
808
+ /**
809
+ * Reads an argument of the running handler by position; `0` is its first
810
+ * parameter. Useful past the fourth, which a handler does not get as a
811
+ * parameter.
812
+ *
813
+ * function onSomething(a: number, b: number, c: number, d: number) {
814
+ * const fifth = arg(4);
815
+ * }
816
+ */
817
+ export function arg(index: number): number {
818
+ return _arg(index);
819
+ }
820
+
821
+ // A string does not cross as text: what arrives is an address in the memory of
822
+ // whoever made the call - the host plugin for a command or a hook, the calling
823
+ // plugin for a native this one exported. The module reads it there.
824
+ /**
825
+ * Reads an argument of the running callback as a string.
826
+ *
827
+ * A forward's strings need no call: its handler gets them as text already.
828
+ */
829
+ export function argText(index: number): string {
830
+ const len = _argText(index, changetype<i32>(textBuf), 512);
831
+ return String.UTF8.decodeUnsafe(changetype<usize>(textBuf), len);
832
+ }
833
+
834
+ /**
835
+ * The number of arguments the running call actually carried.
836
+ *
837
+ * A handler always gets four, padded with zeros, so a native with optional
838
+ * arguments tells a passed zero from a missing one by this:
839
+ *
840
+ * const flashes = argc() >= 2 ? arg(1) : -1;
841
+ */
842
+ export function argc(): number {
843
+ return _argc();
844
+ }
845
+
846
+ /**
847
+ * The `id` of the plugin that called the running exported native, or `-1`.
848
+ *
849
+ * For a native that is about its caller: a cvar registered by a plugin, a
850
+ * chat prefix set for one.
851
+ */
852
+ export function caller(): number {
853
+ return _caller();
854
+ }
855
+
856
+ /**
857
+ * Writes a number back through an argument passed by reference (`&value`
858
+ * in Pawn).
859
+ *
860
+ * Pawn: `set_param_byref`
861
+ */
862
+ export function setArg(index: number, value: number): void {
863
+ _setArg(index, value);
864
+ }
865
+
866
+ /**
867
+ * Writes text back through a string argument.
868
+ *
869
+ * For a callback that hands over a buffer to fill rather than text to read.
870
+ * `max` is the room the caller gave, usually the argument right after the
871
+ * buffer.
872
+ *
873
+ * function placeholder(id: number, target: number, out: number, max: number) {
874
+ * setArgText(2, "ready", max);
875
+ * }
876
+ */
877
+ export function setArgText(index: number, text: string, max: number): void {
878
+ _setArgText(index, text, max);
879
+ }
880
+
881
+ const textBuf = new StaticArray<u8>(512);
882
+
883
+ /** Reads a string argument that a wide handler got as a number. */
884
+ export function argString(pointer: number): string {
885
+ return pointer == 0 ? "" : __cellText(<usize>pointer, 512);
886
+ }
887
+
888
+ /**
889
+ * @hidden A Pawn string at `pointer`: a byte of UTF-8 in each cell, up to the
890
+ * zero cell or `max` cells. AMX Mod X, the engine and the game keep text as
891
+ * bytes - get_amxstring takes the low byte of each cell - so a letter outside
892
+ * ASCII is two or three cells, and a cell per UTF-16 unit came out as
893
+ * mojibake one way and a truncated byte the other ("раз" arrived as "@0").
894
+ */
895
+ export function __cellText(pointer: usize, max: i32): string {
896
+ let length = 0;
897
+ while (length < max && load<i32>(pointer + (<usize>length << 2)) != 0) length++;
898
+
899
+ const bytes = new Uint8Array(length);
900
+ for (let i = 0; i < length; i++) unchecked(bytes[i] = <u8>load<i32>(pointer + (<usize>i << 2)));
901
+ return String.UTF8.decode(bytes.buffer);
902
+ }
903
+
904
+ /**
905
+ * @hidden Text as a Pawn string, a byte of UTF-8 a cell, into `cells` - at
906
+ * most `cells.length - 1` bytes and never half a letter - and the zero cell.
907
+ * Returns how many bytes went in.
908
+ */
909
+ export function __writeCellText(text: string, cells: StaticArray<i32>, at: i32 = 0): i32 {
910
+ const bytes = Uint8Array.wrap(String.UTF8.encode(text));
911
+ let n = min(bytes.length, cells.length - 1 - at);
912
+ if (n < 0) return 0;
913
+ // A continuation byte (10xxxxxx) where the text is cut belongs to a letter
914
+ // that does not fit: leave all of it out.
915
+ if (n < bytes.length) while (n > 0 && (unchecked(bytes[n]) & 0xC0) == 0x80) n--;
916
+ for (let i = 0; i < n; i++) unchecked(cells[at + i] = <i32>unchecked(bytes[i]));
917
+ unchecked(cells[at + n] = 0);
918
+ return n;
919
+ }
920
+
921
+ /** @hidden Text as a Pawn string in a buffer of its own size. */
922
+ export function __cellsOf(text: string): StaticArray<i32> {
923
+ const cells = new StaticArray<i32>(String.UTF8.byteLength(text) + 1);
924
+ __writeCellText(text, cells);
925
+ return cells;
926
+ }
927
+
928
+ /** Reads the text a raw native from `~/natives` wrote into a cell array. `stringToCells` is the other way. */
929
+ export function cellsToString(cells: StaticArray<i32>): string {
930
+ return __cellText(changetype<usize>(cells), cells.length);
931
+ }
932
+
933
+ /** Writes text into a cell array as a Pawn string, for a raw native. */
934
+ export function stringToCells(text: string, cells: StaticArray<i32>): void {
935
+ __writeCellText(text, cells);
936
+ }
937
+
938
+ // The natives in ~/natives take addresses, because that is what Pawn pushes: a
939
+ // string has to be in memory this plugin owns before its address means anything.
940
+ /**
941
+ * Passes a string to a raw native from `~/natives` without declaring a
942
+ * buffer for it:
943
+ *
944
+ * cfg_set_base_dir(cells("myplugin"));
945
+ *
946
+ * Up to eight strings in one call; a ninth overwrites the first. Only for
947
+ * arguments going in: a native that writes text back needs `out()`.
948
+ */
949
+ export function cells(text: string): number {
950
+ ring = (ring + 1) % 8;
951
+ const buffer = unchecked(rings[ring]);
952
+ stringToCells(text, buffer);
953
+ return changetype<i32>(buffer);
954
+ }
955
+
956
+ const rings: StaticArray<i32>[] = [
957
+ new StaticArray<i32>(256), new StaticArray<i32>(256),
958
+ new StaticArray<i32>(256), new StaticArray<i32>(256),
959
+ new StaticArray<i32>(256), new StaticArray<i32>(256),
960
+ new StaticArray<i32>(256), new StaticArray<i32>(256)
961
+ ];
962
+
963
+ let ring: i32 = 0;
964
+
965
+ // Without it a plugin would declare a StaticArray, changetype it and decode it
966
+ // by hand at every call.
967
+ /**
968
+ * A buffer for a raw native to write text into; `text()` reads it back.
969
+ *
970
+ * const name = out();
971
+ * get_user_name(id, name, TEXT_MAX);
972
+ * console.log(text(name));
973
+ *
974
+ * Up to four at once. A number a native writes through a `&reference` needs
975
+ * one too; `cell()` reads it back.
976
+ */
977
+ export function out(): number {
978
+ slot = (slot + 1) % 4;
979
+ const buffer = unchecked(outs[slot]);
980
+ unchecked(buffer[0] = 0);
981
+ return changetype<i32>(buffer);
982
+ }
983
+
984
+ /** Reads the text a native wrote into an `out()` buffer. */
985
+ export function text(buffer: number): string {
986
+ return __cellText(<usize>buffer, TEXT_MAX);
987
+ }
988
+
989
+ // So that a plugin never writes changetype or StaticArray: those are how
990
+ // AssemblyScript reaches raw memory, and they belong here.
991
+ /**
992
+ * A row of numbers for a native to read from or write into: a vector, a list
993
+ * of players, a line of text. Used like an ordinary array - `buffer[0]`,
994
+ * `buffer.float(2)`, `buffer.text()` - and its `address` is what the native
995
+ * takes.
996
+ *
997
+ * const origin = new CellBuffer(3);
998
+ * entity_set_origin(cube, origin.address);
999
+ */
1000
+ export class CellBuffer {
1001
+ private data: StaticArray<i32>;
1002
+
1003
+ constructor(length: number) {
1004
+ this.data = new StaticArray<i32>(length);
1005
+ }
1006
+
1007
+ /** A buffer of three fractional numbers, for a native that takes a vector: an origin, a size, a colour. */
1008
+ static vector(x: f64, y: f64, z: f64): CellBuffer {
1009
+ const buffer = new CellBuffer(3);
1010
+ buffer.setFloat(0, x);
1011
+ buffer.setFloat(1, y);
1012
+ buffer.setFloat(2, z);
1013
+ return buffer;
1014
+ }
1015
+
1016
+ /** The number of cells in the buffer. */
1017
+ get length(): number {
1018
+ return this.data.length;
1019
+ }
1020
+
1021
+ /** The buffer's address, the argument a native that takes an array needs. */
1022
+ get address(): number {
1023
+ return changetype<i32>(this.data);
1024
+ }
1025
+
1026
+ /** The whole number in the cell at `index`: `buffer[0]`. */
1027
+ @operator("[]") get(index: number): number {
1028
+ return this.data[index];
1029
+ }
1030
+
1031
+ /** Puts a whole number in the cell at `index`: `buffer[0] = 5`. */
1032
+ @operator("[]=") set(index: number, value: number): void {
1033
+ this.data[index] = value;
1034
+ }
1035
+
1036
+ /** The fractional number in the cell at `index`, which a native wrote as `Float:`. */
1037
+ float(index: number): f64 {
1038
+ return cellFloat(this.data[index]);
1039
+ }
1040
+
1041
+ /** Puts a fractional number in the cell at `index`, for a native that reads `Float:`. */
1042
+ setFloat(index: number, value: f64): void {
1043
+ this.data[index] = floatCell(value);
1044
+ }
1045
+
1046
+ /** The text a native wrote into the buffer. */
1047
+ text(): string {
1048
+ return cellsToString(this.data);
1049
+ }
1050
+
1051
+ /** Writes text into the buffer from the cell `at`, as a Pawn string. */
1052
+ write(at: number, value: string): void {
1053
+ __writeCellText(value, this.data, <i32>at);
1054
+ }
1055
+
1056
+ /** Sets every cell to `value`: `buffer.fill(0)` clears it. */
1057
+ fill(value: number): void {
1058
+ this.data.fill(value);
1059
+ }
1060
+ }
1061
+
1062
+ // `Array(33).fill(0)` needs a type argument AssemblyScript cannot infer; this
1063
+ // takes the type from `value`.
1064
+ /** An array of `length` copies of `value`: `arrayOf(33, 0)`. Every slot is the same `value` - for objects, give each slot its own. */
1065
+ export function arrayOf<T>(length: number, value: T): T[] {
1066
+ return new Array<T>(length).fill(value);
1067
+ }
1068
+
1069
+ /** Reads the number a native wrote through an `out()` buffer passed as a `&reference`. */
1070
+ export function cell(buffer: number): number {
1071
+ return load<i32>(buffer);
1072
+ }
1073
+
1074
+ /** Puts a number in an `out()` buffer, for a reference a native reads as well as writes. */
1075
+ export function putCell(buffer: number, value: number): number {
1076
+ store<i32>(buffer, value);
1077
+ return buffer;
1078
+ }
1079
+
1080
+ /**
1081
+ * An empty origin for a message sent to one player:
1082
+ *
1083
+ * message_begin(MSG_ONE, msgid, noOrigin(), id);
1084
+ *
1085
+ * Only messages sent to players near a point read the origin.
1086
+ */
1087
+ export function noOrigin(): number[] {
1088
+ return [0, 0, 0];
1089
+ }
1090
+
1091
+ /** The length of text an `out()` buffer holds: `255`. */
1092
+ export const TEXT_MAX: i32 = 255;
1093
+
1094
+ const outs: StaticArray<i32>[] = [
1095
+ new StaticArray<i32>(TEXT_MAX + 1), new StaticArray<i32>(TEXT_MAX + 1),
1096
+ new StaticArray<i32>(TEXT_MAX + 1), new StaticArray<i32>(TEXT_MAX + 1)
1097
+ ];
1098
+
1099
+ let slot: i32 = 0;
1100
+
1101
+ // Scratch for readText, so a read does not allocate per call.
1102
+ const scratch = new StaticArray<i32>(256);
1103
+ const nameBuf = new StaticArray<u8>(64);
1104
+
1105
+ /** A Counter-Strike team, by the name the game gives it: one of `"TERRORIST"`, `"CT"`, `"SPECTATOR"`, `"UNASSIGNED"`. */
1106
+ export type Team = "TERRORIST" | "CT" | "SPECTATOR" | "UNASSIGNED";
1107
+
1108
+ const TEAM_NAMES: Team[] = ["UNASSIGNED", "TERRORIST", "CT", "SPECTATOR"];
1109
+
1110
+ /** A weapon a player can hold, by its class name, e.g. `"weapon_ak47"` or `"weapon_knife"`. */
1111
+ export type WeaponName =
1112
+ | "weapon_p228" | "weapon_scout" | "weapon_hegrenade" | "weapon_xm1014" | "weapon_c4"
1113
+ | "weapon_mac10" | "weapon_aug" | "weapon_smokegrenade" | "weapon_elite" | "weapon_fiveseven"
1114
+ | "weapon_ump45" | "weapon_sg550" | "weapon_galil" | "weapon_famas" | "weapon_usp"
1115
+ | "weapon_glock18" | "weapon_awp" | "weapon_mp5navy" | "weapon_m249" | "weapon_m3"
1116
+ | "weapon_m4a1" | "weapon_tmp" | "weapon_g3sg1" | "weapon_flashbang" | "weapon_deagle"
1117
+ | "weapon_sg552" | "weapon_ak47" | "weapon_knife" | "weapon_p90";
1118
+
1119
+ // Spelled out rather than `WeaponName | "item_kevlar" | ...`: AssemblyScript
1120
+ // reads a union of string literals as `string`, but not one that mixes a named
1121
+ // type in.
1122
+ /** An item `player.give` hands over: a weapon, armour (`"item_kevlar"`, `"item_assaultsuit"`) or the defuse kit (`"item_thighpack"`). */
1123
+ export type ItemName =
1124
+ | "weapon_p228" | "weapon_scout" | "weapon_hegrenade" | "weapon_xm1014" | "weapon_c4"
1125
+ | "weapon_mac10" | "weapon_aug" | "weapon_smokegrenade" | "weapon_elite" | "weapon_fiveseven"
1126
+ | "weapon_ump45" | "weapon_sg550" | "weapon_galil" | "weapon_famas" | "weapon_usp"
1127
+ | "weapon_glock18" | "weapon_awp" | "weapon_mp5navy" | "weapon_m249" | "weapon_m3"
1128
+ | "weapon_m4a1" | "weapon_tmp" | "weapon_g3sg1" | "weapon_flashbang" | "weapon_deagle"
1129
+ | "weapon_sg552" | "weapon_ak47" | "weapon_knife" | "weapon_p90"
1130
+ | "item_kevlar" | "item_assaultsuit" | "item_thighpack";
1131
+
1132
+ const WEAPON_IDS = [
1133
+ "", "weapon_p228", "", "weapon_scout", "weapon_hegrenade", "weapon_xm1014", "weapon_c4",
1134
+ "weapon_mac10", "weapon_aug", "weapon_smokegrenade", "weapon_elite", "weapon_fiveseven",
1135
+ "weapon_ump45", "weapon_sg550", "weapon_galil", "weapon_famas", "weapon_usp",
1136
+ "weapon_glock18", "weapon_awp", "weapon_mp5navy", "weapon_m249", "weapon_m3",
1137
+ "weapon_m4a1", "weapon_tmp", "weapon_g3sg1", "weapon_flashbang", "weapon_deagle",
1138
+ "weapon_sg552", "weapon_ak47", "weapon_knife", "weapon_p90"
1139
+ ];
1140
+
1141
+ /**
1142
+ * The filter of `Player.all`; every field is optional, e.g.
1143
+ * `Player.all({ alive: true, team: "CT" })`.
1144
+ */
1145
+ export interface PlayerFilter {
1146
+ /** Only living players. */
1147
+ alive?: boolean;
1148
+ /** Only dead players. */
1149
+ dead?: boolean;
1150
+ /** Only players of this team, e.g. `"CT"`. */
1151
+ team?: Team;
1152
+ /** Only bots. */
1153
+ bots?: boolean;
1154
+ /** Only people, no bots. */
1155
+ humans?: boolean;
1156
+ }
1157
+
1158
+ /** An AMX Mod X module a plugin can check for, one of `"reapi"`, `"cstrike"`, `"fun"`, `"hamsandwich"`, `"engine"`, `"fakemeta"`. */
1159
+ export type ModuleName = "reapi" | "cstrike" | "fun" | "hamsandwich" | "engine" | "fakemeta";
1160
+
1161
+ // Asked once each: a module is either loaded for the whole map or not at all.
1162
+ const moduleNames: string[] = [];
1163
+ const moduleLoaded: boolean[] = [];
1164
+
1165
+ /**
1166
+ * `true` when the server has the module: `if (hasModule("reapi")) ...`.
1167
+ * Check it before a native that only one module has.
1168
+ *
1169
+ * Pawn: `LibraryExists`, `module_exists`
1170
+ */
1171
+ export function hasModule(name: ModuleName): boolean {
1172
+ const at = moduleNames.indexOf(name);
1173
+ if (at >= 0) return moduleLoaded[at];
1174
+
1175
+ const loaded = LibraryExists(name, LibType_Library) != 0 || module_exists(name) != 0;
1176
+ moduleNames.push(name);
1177
+ moduleLoaded.push(loaded);
1178
+ return loaded;
1179
+ }
1180
+
1181
+ /** Tells everyone's scoreboard a player's frags and deaths - what cs_set_user_deaths sends. */
1182
+ function sendScoreInfo(id: number, frags: number, deaths: number, team: number) {
1183
+ message_begin(MSG_ALL, get_user_msgid("ScoreInfo"), [0, 0, 0], 0);
1184
+ write_byte(id);
1185
+ write_short(frags);
1186
+ write_short(deaths);
1187
+ write_short(0);
1188
+ write_short(team);
1189
+ message_end();
1190
+ }
1191
+
1192
+ /** The options of `player.kill()`. */
1193
+ export interface KillOptions {
1194
+ /** Keeps the player's frags: no penalty for the suicide. */
1195
+ keepFrags?: boolean;
1196
+ }
1197
+
1198
+ // Plugins add fields of their own to this class, shared by every plugin and by
1199
+ // Pawn, with `interface Player { spawnProtected: boolean }` in a
1200
+ // `declare module "~/facade"` block (docs/en/3.game/02.players.md) - not written
1201
+ // here as one block, which the build would take for a declaration of every
1202
+ // plugin's. The build turns each into a getter and a setter over the module's
1203
+ // store and adds them here (scripts/player-fields.ts).
1204
+ /**
1205
+ * A connecting player, in `"connect"`, `"authorized"` and `"putinserver"`: name,
1206
+ * address, SteamID and team, but no health or weapons yet. Every Player is a
1207
+ * Client too.
1208
+ *
1209
+ * ```ts
1210
+ * server.addEventListener("putinserver", (event) => {
1211
+ * print(event.player, `Welcome, ${event.player.name}!`);
1212
+ * });
1213
+ * ```
1214
+ */
1215
+ export interface Client {
1216
+ /** The player's slot, `1` to `32`. */
1217
+ readonly id: number;
1218
+ /** The player's name. */
1219
+ readonly name: string;
1220
+ /** The player's IP address without the port, e.g. `"192.168.0.10"`. */
1221
+ readonly ip: string;
1222
+ /** The player's SteamID, e.g. `"STEAM_0:1:12345"`. A bot has `"BOT"`, HLTV has `"HLTV"`; until Steam confirms the player it is `"STEAM_ID_PENDING"` (wait for the `"authorized"` event), and on a LAN server `"STEAM_ID_LAN"`. */
1223
+ readonly authid: string;
1224
+ /** `true` for a bot. */
1225
+ readonly isBot: boolean;
1226
+ /** `true` while the player is on the server. */
1227
+ readonly isConnected: boolean;
1228
+ /** The player's admin rights, from the letters in `users.ini`: `client.access.includes("Cvar")`. */
1229
+ readonly access: Access[];
1230
+ /** The player's team, one of `"TERRORIST"`, `"CT"`, `"SPECTATOR"` or `"UNASSIGNED"` (until the player joins a team). Setting it moves the player, as `player.team` does. */
1231
+ team: Team;
1232
+ /** `true` when nobody hears the player on the voice chat. Setting it mutes or unmutes him. */
1233
+ muted: boolean;
1234
+ /** `true` when every player hears him on the voice chat, whatever the side. */
1235
+ heardByEveryone: boolean;
1236
+ /** `true` when he hears every player on the voice chat, whatever the side. */
1237
+ hearsEveryone: boolean;
1238
+ /** The language the player reads the server's text in, e.g. `"en"`, `"ru"`: the one `lang.translate` picks for him. */
1239
+ readonly language: string;
1240
+ /** A signal that aborts when the player leaves the server: `fetch(url, { signal: client.signal })`. */
1241
+ readonly signal: AbortSignal;
1242
+ /** Runs a command in the player's console, as if he had typed it: `client.command("stop")`. */
1243
+ command(text: string): void;
1244
+ /** Joins a side as the game joins a player who picks it in the team menu: `client.joinTeam("CT")`. `false` if the game refused. */
1245
+ joinTeam(team: Team): boolean;
1246
+ /** Asks the player's game for one of its cvars: `await client.queryCvar("fps_max")`, the value as text, or `null` when his game has none. */
1247
+ queryCvar(name: string): Promise<string | null>;
1248
+ }
1249
+
1250
+ /**
1251
+ * A player in the game: everything a Client has, plus health, armor, frags,
1252
+ * weapons and the screen.
1253
+ *
1254
+ * An event about a player gives one as `event.player`; `Player.all()`
1255
+ * lists everyone on the server.
1256
+ */
1257
+ export class Player extends PlayerFields implements Client {
1258
+ constructor(id: number) {
1259
+ super(id);
1260
+ }
1261
+
1262
+ /**
1263
+ * The players on the server: `Player.all({ alive: true })`.
1264
+ *
1265
+ * Every field narrows: `{ bots: true }` is bots only, `{ humans: true }`
1266
+ * people only. Without a filter, everyone connected, never an HLTV proxy.
1267
+ *
1268
+ * Pawn: `get_players`
1269
+ */
1270
+ static all(filter: PlayerFilter = {}): Player[] {
1271
+ let flags = "h";
1272
+ if (filter.alive) flags += "a";
1273
+ if (filter.dead) flags += "b";
1274
+ if (filter.humans) flags += "c";
1275
+ if (filter.bots) flags += "d";
1276
+
1277
+ const team = filter.team;
1278
+ if (team != null) flags += "e";
1279
+
1280
+ const ids = playerIds(flags, team ?? "");
1281
+ const list: Player[] = [];
1282
+ for (let i = 0; i < ids.length; i++) list.push(new Player(ids[i]));
1283
+ return list;
1284
+ }
1285
+
1286
+ /**
1287
+ * The player's name.
1288
+ *
1289
+ * Pawn: `get_user_name`
1290
+ */
1291
+ get name(): string {
1292
+ const len = _getName(this.id, changetype<i32>(nameBuf), 64);
1293
+ return String.UTF8.decodeUnsafe(changetype<usize>(nameBuf), len);
1294
+ }
1295
+
1296
+ /**
1297
+ * The player's health. `100` at spawn. Setting it to `0` or less kills the player.
1298
+ *
1299
+ * Pawn: `get_user_health`, `set_user_health`
1300
+ */
1301
+ get health(): number { return _getHealth(this.id); }
1302
+ set health(hp: number) { _setHealth(this.id, hp); }
1303
+
1304
+ /**
1305
+ * The player's armor points: `100` with a bought vest, `0` without one.
1306
+ *
1307
+ * Pawn: `get_user_armor`, `set_user_armor`
1308
+ */
1309
+ get armor(): number { return get_user_armor(this.id); }
1310
+ set armor(value: number) { set_user_armor(this.id, value); }
1311
+
1312
+ /**
1313
+ * The player's frags on the scoreboard.
1314
+ *
1315
+ * Pawn: `get_user_frags`, `set_user_frags`
1316
+ */
1317
+ get frags(): number { return get_user_frags(this.id); }
1318
+ set frags(value: number) { set_user_frags(this.id, value); }
1319
+
1320
+ /**
1321
+ * The player's deaths on the scoreboard. Setting it updates the scoreboard
1322
+ * at once.
1323
+ *
1324
+ * Pawn: `cs_get_user_deaths`, `cs_set_user_deaths`
1325
+ */
1326
+ get deaths(): number {
1327
+ return hasModule("reapi") ? get_member(this.id, m_iDeaths) : cs_get_user_deaths(this.id);
1328
+ }
1329
+
1330
+ /** The deaths on the scoreboard; setting them tells the scoreboard too. */
1331
+ set deaths(value: number) {
1332
+ if (hasModule("reapi")) {
1333
+ set_member(this.id, m_iDeaths, value);
1334
+ sendScoreInfo(this.id, this.frags, value, get_member(this.id, m_iTeam));
1335
+ return;
1336
+ }
1337
+ cs_set_user_deaths(this.id, value);
1338
+ }
1339
+
1340
+ /**
1341
+ * The player's team, one of `"TERRORIST"`, `"CT"`, `"SPECTATOR"` or `"UNASSIGNED"`. Correct
1342
+ * right after a team change too. Setting it moves the player.
1343
+ *
1344
+ * Pawn: `cs_get_user_team`, `rg_set_user_team`
1345
+ */
1346
+ get team(): Team {
1347
+ const index = hasModule("reapi") ? get_member(this.id, m_iTeam) : cs_get_user_team(this.id);
1348
+ return index >= 0 && index < TEAM_NAMES.length ? TEAM_NAMES[index] : "UNASSIGNED";
1349
+ }
1350
+
1351
+ /**
1352
+ * Moves the player to another team, as reapi's rg_set_user_team does: the
1353
+ * model is picked for the new team, the scoreboard is told, and the round's
1354
+ * win conditions are not checked - the caller decides when that happens.
1355
+ */
1356
+ set team(value: Team) {
1357
+ const index = teamCell(value);
1358
+ if (hasModule("reapi")) {
1359
+ rg_set_user_team(this.id, index, MODEL_AUTO, true, false);
1360
+ return;
1361
+ }
1362
+ // cstrike keeps the model unless told: the first one of the new side.
1363
+ const model = value == "TERRORIST" ? CS_T_TERROR : value == "CT" ? CS_CT_URBAN : CS_DONTCHANGE;
1364
+ cs_set_user_team(this.id, index, model);
1365
+ }
1366
+
1367
+ /**
1368
+ * Joins a side the way the game joins a player who picks it in the team
1369
+ * menu, appearance picked for him: `player.joinTeam("CT")`. A player who
1370
+ * has just arrived is in the game after it and can spawn, which
1371
+ * `player.team = ...` does not do for him. `false` if the game refused.
1372
+ *
1373
+ * Pawn: `rg_join_team`
1374
+ */
1375
+ joinTeam(team: Team): boolean {
1376
+ if (team == "UNASSIGNED") return false;
1377
+ if (hasModule("reapi")) return rg_join_team(this.id, teamCell(team)) != 0;
1378
+
1379
+ // Without reapi, what the player would type: the team menu's slot, then
1380
+ // the appearance menu's automatic pick.
1381
+ engclient_cmd(this.id, "jointeam", team == "TERRORIST" ? "1" : team == "CT" ? "2" : "6");
1382
+ if (team != "SPECTATOR") engclient_cmd(this.id, "joinclass", "5");
1383
+ return this.team == team;
1384
+ }
1385
+
1386
+ /**
1387
+ * The player's IP address without the port, e.g. `"192.168.0.10"`.
1388
+ *
1389
+ * Pawn: `get_user_ip`
1390
+ */
1391
+ get ip(): string {
1392
+ // The fourth argument drops the port, which is what a plugin means by
1393
+ // an address nine times out of ten.
1394
+ return get_user_ip(this.id, 1);
1395
+ }
1396
+
1397
+ /**
1398
+ * The player's SteamID, e.g. `"STEAM_0:1:12345"`. A bot has `"BOT"`, HLTV has `"HLTV"`; until Steam confirms the player it is `"STEAM_ID_PENDING"` (wait for the `"authorized"` event), and on a LAN server `"STEAM_ID_LAN"`.
1399
+ *
1400
+ * Pawn: `get_user_authid`
1401
+ */
1402
+ get authid(): string {
1403
+ return get_user_authid(this.id);
1404
+ }
1405
+
1406
+ /**
1407
+ * `true` while the player is alive.
1408
+ *
1409
+ * Pawn: `is_user_alive`
1410
+ */
1411
+ get isAlive(): boolean { return is_user_alive(this.id) != 0; }
1412
+ /**
1413
+ * `true` while the player is on the server.
1414
+ *
1415
+ * Pawn: `is_user_connected`
1416
+ */
1417
+ get isConnected(): boolean { return is_user_connected(this.id) != 0; }
1418
+ /**
1419
+ * `true` for a bot.
1420
+ *
1421
+ * Pawn: `is_user_bot`
1422
+ */
1423
+ get isBot(): boolean { return is_user_bot(this.id) != 0; }
1424
+
1425
+ /**
1426
+ * A signal that aborts when the player leaves the server, with an Error named
1427
+ * `"AbortError"`; the next player in the slot gets a new one.
1428
+ *
1429
+ * ```ts
1430
+ * const response = await fetch(url, { signal: player.signal });
1431
+ * ```
1432
+ *
1433
+ * An async command handler or player event already runs under it: its
1434
+ * `await`s stop quietly when the player leaves.
1435
+ */
1436
+ get signal(): AbortSignal { return __co_player_signal(this.id); }
1437
+
1438
+ /**
1439
+ * `true` when nobody hears the player on the voice chat, with alltalk or
1440
+ * without. Setting it mutes or unmutes him; his other voice settings stay.
1441
+ *
1442
+ * Pawn: `set_speak`, `SPEAK_MUTED`
1443
+ */
1444
+ get muted(): boolean { return (get_speak(this.id) & SPEAK_MUTED) != 0; }
1445
+ set muted(value: boolean) { this.setSpeak(SPEAK_MUTED, value); }
1446
+
1447
+ /**
1448
+ * `true` when every player hears him on the voice chat, whatever the side
1449
+ * and without alltalk. A mute (`muted`) still silences him.
1450
+ *
1451
+ * Pawn: `set_speak`, `SPEAK_ALL`
1452
+ */
1453
+ get heardByEveryone(): boolean { return (get_speak(this.id) & SPEAK_ALL) != 0; }
1454
+ set heardByEveryone(value: boolean) { this.setSpeak(SPEAK_ALL, value); }
1455
+
1456
+ /**
1457
+ * `true` when he hears every player on the voice chat, whatever the side
1458
+ * and without alltalk: a spectator who hears both sides.
1459
+ *
1460
+ * Pawn: `set_speak`, `SPEAK_LISTENALL`
1461
+ */
1462
+ get hearsEveryone(): boolean { return (get_speak(this.id) & SPEAK_LISTENALL) != 0; }
1463
+ set hearsEveryone(value: boolean) { this.setSpeak(SPEAK_LISTENALL, value); }
1464
+
1465
+ // One voice setting of set_speak's, the others kept.
1466
+ private setSpeak(flag: i32, on: bool): void {
1467
+ const flags = get_speak(this.id);
1468
+ set_speak(this.id, on ? flags | flag : flags & ~flag);
1469
+ }
1470
+
1471
+ /**
1472
+ * The language the player reads the server's text in, e.g. `"en"`, `"ru"`:
1473
+ * the one `lang.translate` picks for him. It is his `setinfo lang`, or the
1474
+ * server's language when he has none or `amx_client_languages` is `0`.
1475
+ *
1476
+ * Pawn: `get_user_info(id, "lang")`, `amx_language`
1477
+ */
1478
+ get language(): string {
1479
+ // AMX Mod X's own rule (playerlang, translate): a language starts with a letter.
1480
+ const own = get_cvar_num("amx_client_languages") != 0 ? get_user_info(this.id, "lang") : "";
1481
+ const first = own.length > 0 ? own.charCodeAt(0) | 0x20 : 0;
1482
+ return first >= 0x61 && first <= 0x7a ? own : get_cvar_string("amx_language");
1483
+ }
1484
+
1485
+ /**
1486
+ * Shows a line of text on the player's screen:
1487
+ * `player.showHud("-35 HP", { color: [255, 40, 40], x: 0.02, y: 0.88, hold: 2 })`.
1488
+ * Every option has a default.
1489
+ *
1490
+ * Pawn: `set_hudmessage`, `show_hudmessage`
1491
+ */
1492
+ showHud(text: string, options: HudOptions = {}): void {
1493
+ showHudTo(this.id, text, options);
1494
+ }
1495
+
1496
+ /**
1497
+ * Plays a sound to the player alone, the way the radio does - heard as it
1498
+ * is wherever he stands: `player.playSound("vox/one.wav")`. The path is
1499
+ * under `sound/`, as `server.precache` takes it.
1500
+ *
1501
+ * Pawn: `SendAudio`, `rg_send_audio`
1502
+ */
1503
+ playSound(sample: string): void {
1504
+ message_begin(MSG_ONE, get_user_msgid("SendAudio"), [0, 0, 0], this.id);
1505
+ write_byte(this.id);
1506
+ write_string(sample);
1507
+ write_short(100);
1508
+ message_end();
1509
+ }
1510
+
1511
+ /** The player's screen effects: `player.screen.fade({ ... })`, `.shake(...)`, `.statusIcon(...)` — see Screen. */
1512
+ get screen(): Screen { return new Screen(this.id); }
1513
+
1514
+ // ------------------------------------------------------------ actions
1515
+ //
1516
+ // reapi where the server has it, the stock AMX Mod X modules where it does
1517
+ // not: fun, cstrike, hamsandwich and the core.
1518
+
1519
+ /**
1520
+ * Gives the player a weapon or an item: `player.give("weapon_flashbang")`.
1521
+ * `false` if the game did not give it.
1522
+ *
1523
+ * Pawn: `rg_give_item`, `give_item`
1524
+ */
1525
+ give(item: ItemName): boolean {
1526
+ if (hasModule("reapi")) return rg_give_item(this.id, item) > 0;
1527
+ return give_item(this.id, item) > 0;
1528
+ }
1529
+
1530
+ /**
1531
+ * Takes all the player's weapons away. The suit (armour, HUD) stays unless
1532
+ * `removeSuit` is `true`.
1533
+ *
1534
+ * Pawn: `rg_remove_all_items`, `strip_user_weapons`
1535
+ */
1536
+ removeAllItems(removeSuit: boolean = false): void {
1537
+ if (hasModule("reapi")) {
1538
+ rg_remove_all_items(this.id, removeSuit);
1539
+ return;
1540
+ }
1541
+ strip_user_weapons(this.id);
1542
+ }
1543
+
1544
+ /**
1545
+ * Sets the player's reserve ammo for a weapon he carries: `player.setAmmo("weapon_flashbang", 2)`.
1546
+ *
1547
+ * Pawn: `rg_set_user_bpammo`, `cs_set_user_bpammo`
1548
+ */
1549
+ setAmmo(weapon: WeaponName, amount: number): void {
1550
+ const id = WEAPON_IDS.indexOf(weapon);
1551
+ if (id <= 0) return;
1552
+ if (hasModule("reapi")) rg_set_user_bpammo(this.id, id, amount);
1553
+ else cs_set_user_bpammo(this.id, id, amount);
1554
+ }
1555
+
1556
+ /**
1557
+ * The player's reserve ammo for a weapon he carries: `player.getAmmo("weapon_ak47")`;
1558
+ * for a grenade, how many he has. `0` for a weapon he does not carry.
1559
+ *
1560
+ * Pawn: `rg_get_user_bpammo`, `cs_get_user_bpammo`
1561
+ */
1562
+ getAmmo(weapon: WeaponName): number {
1563
+ const id = WEAPON_IDS.indexOf(weapon);
1564
+ if (id <= 0) return 0;
1565
+ return hasModule("reapi") ? rg_get_user_bpammo(this.id, id) : cs_get_user_bpammo(this.id, id);
1566
+ }
1567
+
1568
+ /**
1569
+ * Respawns the player in the current round, at a spawn point the game picks.
1570
+ *
1571
+ * Pawn: `rg_round_respawn`
1572
+ */
1573
+ respawn(): void {
1574
+ if (hasModule("reapi")) rg_round_respawn(this.id);
1575
+ else ExecuteHamB(Ham_CS_RoundRespawn, this.id);
1576
+ }
1577
+
1578
+ /**
1579
+ * Kills the player, as the `kill` console command does. With
1580
+ * `{ keepFrags: true }` the death costs no frags.
1581
+ *
1582
+ * Pawn: `user_kill`, `user_silentkill`
1583
+ */
1584
+ kill(options: KillOptions = {}): void {
1585
+ user_kill(this.id, options.keepFrags ? 1 : 0);
1586
+ }
1587
+
1588
+ /**
1589
+ * The player's admin rights, from the letters in `users.ini`:
1590
+ * `player.access.includes("Cvar")`.
1591
+ *
1592
+ * Pawn: `get_user_flags`
1593
+ */
1594
+ get access(): Access[] {
1595
+ return <Access[]>ACCESS.namesOf(get_user_flags(this.id));
1596
+ }
1597
+
1598
+ /**
1599
+ * Puts a weapon the player carries into his hands. `false` if he does not
1600
+ * have it.
1601
+ *
1602
+ * Pawn: `rg_switch_weapon`
1603
+ */
1604
+ switchWeapon(weapon: WeaponName): boolean {
1605
+ if (hasModule("reapi")) {
1606
+ const entity = rg_find_weapon_bpack_by_name(this.id, weapon);
1607
+ return entity > 0 && rg_switch_weapon(this.id, entity) != 0;
1608
+ }
1609
+
1610
+ const id = WEAPON_IDS.indexOf(weapon);
1611
+ if (id <= 0 || user_has_weapon(this.id, id) == 0) return false;
1612
+ engclient_cmd(this.id, weapon);
1613
+ return true;
1614
+ }
1615
+
1616
+ /**
1617
+ * Recomputes the player's speed from the weapon in hand, after a slowdown for
1618
+ * instance.
1619
+ *
1620
+ * Pawn: `rg_reset_maxspeed`
1621
+ */
1622
+ resetMaxSpeed(): void {
1623
+ if (hasModule("reapi")) rg_reset_maxspeed(this.id);
1624
+ else ExecuteHamB(Ham_CS_Player_ResetMaxSpeed, this.id);
1625
+ }
1626
+
1627
+ /**
1628
+ * Runs a command in the player's own console, as if he had typed it:
1629
+ * `player.command("messagemode say_team")`, `player.command("stop")`.
1630
+ * The player's game runs it, not the server.
1631
+ *
1632
+ * Pawn: `client_cmd`
1633
+ */
1634
+ command(text: string): void {
1635
+ new Call(NATIVE_client_cmd).num(this.id).str("%s").str(text).run();
1636
+ }
1637
+
1638
+ /**
1639
+ * Asks the player's game for one of its cvars: `await
1640
+ * player.queryCvar("fps_max")` is the value as text, e.g. `"100"`, or
1641
+ * `null` when his game has no such cvar or will not tell. The answer is
1642
+ * what his game says, a claim a cheat can change. A bot has no game to ask
1643
+ * and answers `null` at once; when the player leaves before he answers, the
1644
+ * promise is rejected with an `"AbortError"`.
1645
+ *
1646
+ * Pawn: `query_client_cvar`
1647
+ */
1648
+ queryCvar(name: string): Promise<string | null> {
1649
+ const promise = __co_promise<string | null>();
1650
+
1651
+ if (this.isBot || !this.isConnected) {
1652
+ __co_resolve<string | null>(promise, null);
1653
+ return promise;
1654
+ }
1655
+
1656
+ if (cvarAnswer.length == 0) cvarAnswer = callbackName(cvarAnswered, "querycvar");
1657
+ const query = new CvarQuery(this.id, name.toLowerCase(), promise);
1658
+ query.guard = new __AbortGuard(query, this.signal);
1659
+ cvarQueries.push(query);
1660
+ query_client_cvar(this.id, name, cvarAnswer);
1661
+ return promise;
1662
+ }
1663
+ }
1664
+
1665
+ // ---------------------------------------------------------------- a client's cvars
1666
+
1667
+ // The questions sent and not answered yet, in the order asked. The engine
1668
+ // hands every answer to one public, with the player and the cvar's name.
1669
+ class CvarQuery extends __AbortWatch {
1670
+ guard: __AbortGuard | null = null;
1671
+
1672
+ constructor(public player: i32, public name: string, public promise: Promise<string | null>) {
1673
+ super();
1674
+ }
1675
+
1676
+ /** The player left, or what awaits the answer gave up. */
1677
+ run(reason: Error): void {
1678
+ forgetQuery(this);
1679
+ this.promise.__reject(reason);
1680
+ }
1681
+ }
1682
+
1683
+ const cvarQueries: CvarQuery[] = [];
1684
+ let cvarAnswer = "";
1685
+
1686
+ function forgetQuery(query: CvarQuery): void {
1687
+ const at = cvarQueries.indexOf(query);
1688
+ if (at >= 0) cvarQueries.splice(at, 1);
1689
+ (query.guard as __AbortGuard).release();
1690
+ }
1691
+
1692
+ /** public cvar_answer(id, const cvar[], const value[], const param[]) */
1693
+ function cvarAnswered(id: number, cvar: number, value: number, param: number): void {
1694
+ const name = argText(1).toLowerCase();
1695
+
1696
+ for (let i = 0; i < cvarQueries.length; i++) {
1697
+ const query = cvarQueries[i];
1698
+ if (query.player != <i32>id || query.name != name) continue;
1699
+
1700
+ forgetQuery(query);
1701
+ // What the engine answers for a cvar the client has not, or will not tell.
1702
+ const text = argText(2);
1703
+ const unknown = text == "Bad CVAR request" || text == "CVAR is privileged";
1704
+ __co_resolve<string | null>(query.promise, unknown ? null : text);
1705
+ return;
1706
+ }
1707
+ }
1708
+
1709
+ /**
1710
+ * The public name that calls `handler`, whether the slot is new or taken back
1711
+ * after a reload: for a native handed a callback on every call
1712
+ * (query_client_cvar), not a registration made once (see publicFor).
1713
+ */
1714
+ function callbackName(handler: WideHandler, key: string): string {
1715
+ const slot = _slot(hostIndex(handler, true), SHAPE_WIDE, key, 0);
1716
+ return slot < 0 ? "" : `__amxts_cb${slot & 0xffff}`;
1717
+ }
1718
+
1719
+ // A Pawn string is one character per cell, so calling such a native directly
1720
+ // means a StaticArray<i32>, its address, and decoding it afterwards.
1721
+ /**
1722
+ * Calls a native that fills a text buffer and returns the text. The native
1723
+ * itself is passed in:
1724
+ *
1725
+ * readText(get_mapname) // "c21_kitty"
1726
+ * readText(get_user_name, 32, id) // not this one: see Player.name
1727
+ */
1728
+ export function readText(fill: (out: number, max: number) => number, max: number = 255): string {
1729
+ fill(changetype<i32>(scratch), min(max, 255));
1730
+ return cellsToString(scratch);
1731
+ }
1732
+
1733
+ /**
1734
+ * The ids of the players on the server, as an array. `flags`: `"a"` living,
1735
+ * `"b"` dead, `"c"` no bots, `"h"` no HLTV, `"e"` only `team`. `Player.all` does the
1736
+ * same with readable options.
1737
+ *
1738
+ * Pawn: `get_players`
1739
+ */
1740
+ export function playerIds(flags: string = "", team: string = ""): number[] {
1741
+ const list = new StaticArray<i32>(33);
1742
+ const count = new StaticArray<i32>(1);
1743
+
1744
+ get_players(changetype<i32>(list), changetype<i32>(count), cells(flags), cells(team));
1745
+
1746
+ const ids: number[] = [];
1747
+ for (let i = 0; i < unchecked(count[0]); i++) ids.push(unchecked(list[i]));
1748
+ return ids;
1749
+ }
1750
+
1751
+ // Server's event name is typed like a DOM one - `K extends keyof ServerEventMap` -
1752
+ // so an editor completes it, refuses a misspelled one, and hands the listener
1753
+ // the event's own type. The compiler reads the same signature through a patch
1754
+ // (runtime/patches): the name has to be written out as a string literal,
1755
+ // because a string held in a variable says nothing about which event it is.
1756
+ // Its words for the plugin author are the Server entry of scripts/docs/as/facade.ts.
1757
+
1758
+ // ---------------------------------------------------------------- commands
1759
+
1760
+ /** A command's handler: gets the player who typed it and the words after the command's name. */
1761
+ export type CommandHandler = (player: Player, args: string[]) => void;
1762
+
1763
+ /** The options of a command: who may use it and its description in a listing. */
1764
+ export interface CommandOptions {
1765
+ /** The admin right a player needs to use the command; left out, everyone may. */
1766
+ access?: Access;
1767
+ /** The command's description, shown by `amx_help` and the like. */
1768
+ description?: string;
1769
+ }
1770
+
1771
+ // The commands of this plugin, by name. One trampoline serves them all - the
1772
+ // host calls it by its table index - and it finds the handler by the name the
1773
+ // player typed.
1774
+ const commandNames: string[] = [];
1775
+ const commandHandlers: CommandHandler[] = [];
1776
+ const commandAccess: string[] = [];
1777
+ let chatHooked = false;
1778
+
1779
+ function findCommand(name: string): i32 {
1780
+ const lower = name.toLowerCase();
1781
+ for (let i = 0; i < commandNames.length; i++) {
1782
+ if (commandNames[i] == lower) return i;
1783
+ }
1784
+ return -1;
1785
+ }
1786
+
1787
+ /**
1788
+ * Converts `users.ini` letters to rights: `accessOf("abc")` is
1789
+ * [`"Immunity"`, `"Reservation"`, `"Kick"`]. An unknown letter is skipped.
1790
+ *
1791
+ * Pawn: `read_flags`
1792
+ */
1793
+ export function accessOf(letters: string): Access[] {
1794
+ return <Access[]>ACCESS.namesOf(read_flags(letters));
1795
+ }
1796
+
1797
+ /** Whether a player holds the admin flag a command asks for. */
1798
+ function mayRun(id: i32, at: i32): bool {
1799
+ const access = commandAccess[at];
1800
+ if (access.length == 0) return true;
1801
+ return (get_user_flags(id) & ACCESS.bitOf(access)) != 0;
1802
+ }
1803
+
1804
+ /** say /name a b - the chat text split on spaces, the first word the name. */
1805
+ function chatCommand(player: number, level: number, cid: number, unused: number): void {
1806
+ const id = <i32>player;
1807
+ const text = read_argv(1).trim();
1808
+ const slash = text.startsWith("/");
1809
+
1810
+ // "/name a b" - or a phrase added as "say <phrase>", which is the whole line.
1811
+ const words = text.split(" ").filter((word: string) => word.length > 0);
1812
+ const at = slash && words.length > 0 ? findCommand(words[0]) : findCommand("say " + text);
1813
+ if (at < 0 || !mayRun(id, at)) { _outcome(0); return; }
1814
+ if (!slash) words.length = 1;
1815
+ runCommand(at, id, words.slice(1));
1816
+ }
1817
+
1818
+ /** name a b - a console command, its arguments as the engine split them. */
1819
+ function consoleCommand(player: number, level: number, cid: number, unused: number): void {
1820
+ const id = <i32>player;
1821
+ const at = findCommand(read_argv(0));
1822
+ if (at < 0 || !mayRun(id, at)) { _outcome(0); return; }
1823
+ runCommand(at, id, commandArgs());
1824
+ }
1825
+
1826
+ /**
1827
+ * Runs a player's command. An async handler runs under the player's signal
1828
+ * (see __co_ambient_player); the command is handled, so a chat command is not
1829
+ * repeated in chat, as a Pawn command's PLUGIN_HANDLED does.
1830
+ */
1831
+ function runCommand(at: i32, id: i32, args: string[]): void {
1832
+ const ambient = __co_ambient_player;
1833
+ __co_ambient_player = id;
1834
+ commandHandlers[at](new Player(id), args);
1835
+ __co_ambient_player = ambient;
1836
+ handled();
1837
+ }
1838
+
1839
+ /** The words after the command's name, as the engine split them. */
1840
+ function commandArgs(): string[] {
1841
+ const args: string[] = [];
1842
+ const count = read_argc();
1843
+ for (let i = 1; i < count; i++) args.push(read_argv(i));
1844
+ return args;
1845
+ }
1846
+
1847
+ /** A server command's handler: gets the words after the command's name. */
1848
+ export type ServerCommandHandler = (args: string[]) => void;
1849
+
1850
+ // The server commands of this plugin, by name, and the ones still waiting for
1851
+ // plugin_init. One trampoline serves them all, as it does the player commands.
1852
+ const serverCommandNames: string[] = [];
1853
+ const serverCommandHandlers: ServerCommandHandler[] = [];
1854
+ const waitingServerCommands: string[] = [];
1855
+
1856
+ // A touch the engine module filters by class, so a touch nobody listens for -
1857
+ // and there is one every frame for a player on the ground - never reaches the
1858
+ // plugin. One register_touch per pair of classes, its listeners behind it.
1859
+ class TouchFilter {
1860
+ listeners: TouchListener[] = [];
1861
+ constructor(public toucher: string, public touched: string) {}
1862
+ }
1863
+
1864
+ const touchFilters: TouchFilter[] = [];
1865
+ const waitingTouches: TouchFilter[] = [];
1866
+
1867
+ // A Ham Sandwich hook waiting for plugin_init: RegisterHam makes an entity of
1868
+ // the class to find its function, which is not for the moment a plugin loads.
1869
+ class HamRegistration {
1870
+ constructor(public fn: i32, public classname: string, public handler: WideHandler, public post: bool) {}
1871
+ }
1872
+
1873
+ const waitingHams: HamRegistration[] = [];
1874
+
1875
+ function registerHam(registration: HamRegistration): void {
1876
+ _ham(registration.fn, registration.classname, hostIndex(registration.handler, true), registration.post ? 1 : 0);
1877
+ }
1878
+
1879
+ /**
1880
+ * @hidden The hood of a game event Ham Sandwich delivers (as/hooks.ts):
1881
+ * `fn` hooked on the class, a reload taking its slot back.
1882
+ */
1883
+ export function __ham(fn: i32, classname: string, handler: WideHandler, post: bool): void {
1884
+ const registration = new HamRegistration(fn, classname, handler, post);
1885
+ if (serverUp) registerHam(registration);
1886
+ else waitingHams.push(registration);
1887
+ }
1888
+
1889
+ /** name a b - typed in the server console or sent over rcon. */
1890
+ function serverCommand(a: number, b: number, c: number, d: number): void {
1891
+ const at = serverCommandNames.indexOf(read_argv(0).toLowerCase());
1892
+ if (at < 0) { _outcome(0); return; }
1893
+
1894
+ serverCommandHandlers[at](commandArgs());
1895
+ handled();
1896
+ }
1897
+
1898
+ /**
1899
+ * Hands one name to the engine. Not from a plugin's top level: that runs
1900
+ * during plugin_natives, and a command registered then crashes the server
1901
+ * when it is typed (see publicFor). A reload takes the same public back, and
1902
+ * the engine still knows the name, so nothing is registered twice.
1903
+ */
1904
+ function registerServerCommand(name: string): void {
1905
+ const pub = publicFor(serverCommand, "srvcmd:" + name, 1);
1906
+ if (pub.length > 0) register_srvcmd(name, pub);
1907
+ }
1908
+
1909
+ // ---------------------------------------------------------------- HUD
1910
+
1911
+ /**
1912
+ * The look of a HUD message. Every field has a default, so
1913
+ * `{ color: [255, 40, 40] }` is enough.
1914
+ *
1915
+ * Pawn: `set_hudmessage`
1916
+ */
1917
+ export interface HudOptions {
1918
+ /** The text's colour: red, green, blue, `0` to `255` each. */
1919
+ color?: number[];
1920
+ /** The horizontal position: `0` is the left edge, `1` the right; `-1` centres the text. */
1921
+ x?: number;
1922
+ /** The vertical position: `0` is the top, `1` the bottom; `-1` centres the text. */
1923
+ y?: number;
1924
+ /** The time the message stays on screen, in seconds. */
1925
+ hold?: number;
1926
+ /** The appearance effect, one of: `"fade"` in and out, `"flicker"`, or `"typewriter"` — letter by letter. */
1927
+ effect?: HudEffect;
1928
+ /** The fade-in time, in seconds. */
1929
+ fadeIn?: number;
1930
+ /** The fade-out time, in seconds. */
1931
+ fadeOut?: number;
1932
+ /** The HUD channel, `1` to `4`; `-1` picks a free one. */
1933
+ channel?: number;
1934
+ /** The duration of the `"flicker"` and `"typewriter"` effects, in seconds. */
1935
+ effectTime?: number;
1936
+ /**
1937
+ * Large letters, for a result or a headline. They have no channels, so
1938
+ * `channel` does not apply.
1939
+ *
1940
+ * Pawn: `set_dhudmessage`
1941
+ */
1942
+ large?: boolean;
1943
+ }
1944
+
1945
+ /** A HUD message's appearance effect, one of: `"fade"` in and out, `"flicker"`, or `"typewriter"` — letter by letter. */
1946
+ export type HudEffect = "fade" | "flicker" | "typewriter";
1947
+
1948
+ function hudEffect(options: HudOptions): i32 {
1949
+ return options.effect == "flicker" ? 1 : options.effect == "typewriter" ? 2 : 0;
1950
+ }
1951
+
1952
+ // A field left out is AMX Mod X's own default, set_hudmessage's.
1953
+ function setHud(options: HudOptions): void {
1954
+ const color = options.color ?? [200, 100, 0];
1955
+ const red = color.length > 0 ? color[0] : 200;
1956
+ const green = color.length > 1 ? color[1] : 100;
1957
+ const blue = color.length > 2 ? color[2] : 0;
1958
+ const x = options.x ?? -1.0;
1959
+ const y = options.y ?? 0.35;
1960
+ const effectTime = options.effectTime ?? 6.0;
1961
+ const hold = options.hold ?? 12.0;
1962
+ const fadeIn = options.fadeIn ?? 0.1;
1963
+ const fadeOut = options.fadeOut ?? 0.2;
1964
+ if (options.large) {
1965
+ set_dhudmessage(red, green, blue, x, y, hudEffect(options), effectTime, hold, fadeIn, fadeOut);
1966
+ return;
1967
+ }
1968
+ set_hudmessage(red, green, blue, x, y, hudEffect(options), effectTime, hold, fadeIn, fadeOut, options.channel ?? -1);
1969
+ }
1970
+
1971
+ function showHudTo(id: i32, text: string, options: HudOptions): void {
1972
+ setHud(options);
1973
+ if (options.large) show_dhudmessage(id, text);
1974
+ else show_hudmessage(id, text);
1975
+ }
1976
+
1977
+ /**
1978
+ * A HUD line for one message: showing a new one replaces the old instead of
1979
+ * taking another channel — for a countdown redrawn every second, a warning
1980
+ * that changes. `clear` removes it early.
1981
+ *
1982
+ * ```ts
1983
+ * const countdown = new HudLine();
1984
+ * countdown.show(player, `${left}`, { color: [255, 50, 50], hold: 1.1 });
1985
+ * countdown.clear(player);
1986
+ * countdown.clearAll();
1987
+ * ```
1988
+ *
1989
+ * Pawn: `CreateHudSyncObj`, `ShowSyncHudMsg`
1990
+ */
1991
+ export class HudLine {
1992
+ private handle: i32 = -1;
1993
+
1994
+ /** Shows `text` to the player on this line, replacing what it showed him before. */
1995
+ show(player: Player, text: string, options: HudOptions = {}): void {
1996
+ if (this.handle < 0) this.handle = <i32>CreateHudSyncObj();
1997
+ setHud(options);
1998
+ ShowSyncHudMsg(player.id, this.handle, text);
1999
+ }
2000
+
2001
+ /** Removes the line's message from the player's screen before its time is up. */
2002
+ clear(player: Player): void {
2003
+ if (this.handle >= 0) ClearSyncHud(player.id, this.handle);
2004
+ }
2005
+
2006
+ /** Removes the line's message from every screen. */
2007
+ clearAll(): void {
2008
+ if (this.handle >= 0) ClearSyncHud(0, this.handle);
2009
+ }
2010
+ }
2011
+
2012
+ // ---------------------------------------------------------------- the screen
2013
+
2014
+ /** A fade's direction, one of: `"in"` from the colour to a clear view, `"out"` from a clear view to the colour. */
2015
+ export type FadeDirection = "in" | "out";
2016
+
2017
+ /** The options of `player.screen.fade`. Times are in seconds. */
2018
+ export interface FadeOptions {
2019
+ /** The colour: red, green, blue and alpha, `0` to `255` each; black by default. */
2020
+ color?: number[];
2021
+ /** The fade's duration, in seconds; `1` by default. */
2022
+ duration?: number;
2023
+ /** The time the full colour holds, in seconds; `0` by default. */
2024
+ hold?: number;
2025
+ /** The fade's direction, one of: `"in"` (the default) from the colour to a clear view, `"out"` from a clear view to the colour. */
2026
+ direction?: FadeDirection;
2027
+ /** Keeps the colour on the screen until the next fade. */
2028
+ stay?: boolean;
2029
+ /** Tints what is on the screen rather than painting over it. */
2030
+ modulate?: boolean;
2031
+ }
2032
+
2033
+ /** The options of `player.screen.shake`. */
2034
+ export interface ShakeOptions {
2035
+ /** The shake's strength: how far the view moves, up to 16 units; `4` by default. */
2036
+ amplitude?: number;
2037
+ /** The shake's duration, in seconds; `1` by default. */
2038
+ duration?: number;
2039
+ /** The shake's frequency, in jolts a second; `5` by default. */
2040
+ frequency?: number;
2041
+ }
2042
+
2043
+ /** A status icon's state, one of `"hide"`, `"show"` (lit) or `"flash"`. */
2044
+ export type StatusIconState = "hide" | "show" | "flash";
2045
+
2046
+ /** A time or a size as a message's fixed point: `units` of it make one, capped at 16 bits. */
2047
+ function fixed(value: number, units: number): i32 {
2048
+ return <i32>Math.max(0.0, Math.min(65535.0, Math.round(value * units)));
2049
+ }
2050
+
2051
+ /**
2052
+ * The effects one player sees over the world: fades, shakes, status icons and
2053
+ * the parts of the HUD the game draws itself.
2054
+ *
2055
+ * ```ts
2056
+ * player.screen.fade({ color: [0, 0, 0, 255], duration: 0.5, hold: 1, stay: true });
2057
+ * player.screen.shake({ amplitude: 8, duration: 1, frequency: 5 });
2058
+ * player.screen.statusIcon("dmg_cold", "show", [0, 160, 255]);
2059
+ * ```
2060
+ *
2061
+ * Times are in seconds.
2062
+ *
2063
+ * Pawn: `ScreenFade`, `ScreenShake`, `StatusIcon`, ...
2064
+ */
2065
+ export class Screen {
2066
+ constructor(private id: i32) {}
2067
+
2068
+ private begin(name: string, reliable: bool = true): void {
2069
+ message_begin(reliable ? MSG_ONE : MSG_ONE_UNRELIABLE, get_user_msgid(name), [0, 0, 0], this.id);
2070
+ }
2071
+
2072
+ /**
2073
+ * Colours the player's screen, fading in or out.
2074
+ *
2075
+ * Pawn: `ScreenFade`
2076
+ */
2077
+ fade(options: FadeOptions = {}): void {
2078
+ const color = options.color ?? [0, 0, 0, 255];
2079
+ let flags = options.direction == "out" ? FFADE_OUT : FFADE_IN;
2080
+ if (options.modulate) flags |= FFADE_MODULATE;
2081
+ if (options.stay) flags |= FFADE_STAYOUT;
2082
+
2083
+ this.begin("ScreenFade");
2084
+ write_short(fixed(options.duration ?? 1.0, 4096.0));
2085
+ write_short(fixed(options.hold ?? 0.0, 4096.0));
2086
+ write_short(flags);
2087
+ for (let i = 0; i < 4; i++) write_byte(i < color.length ? <i32>color[i] : 255);
2088
+ message_end();
2089
+ }
2090
+
2091
+ /**
2092
+ * Shakes the player's view.
2093
+ *
2094
+ * Pawn: `ScreenShake`
2095
+ */
2096
+ shake(options: ShakeOptions = {}): void {
2097
+ this.begin("ScreenShake");
2098
+ write_short(fixed(options.amplitude ?? 4.0, 4096.0));
2099
+ write_short(fixed(options.duration ?? 1.0, 4096.0));
2100
+ write_short(fixed(options.frequency ?? 5.0, 256.0));
2101
+ message_end();
2102
+ }
2103
+
2104
+ /**
2105
+ * Shows, flashes or hides a status icon by its sprite name (`"dmg_cold"`,
2106
+ * `"buyzone"`, `"c4"`, ...), in a colour.
2107
+ *
2108
+ * Pawn: `StatusIcon`
2109
+ */
2110
+ statusIcon(sprite: string, state: StatusIconState, color: number[] = [0, 160, 0]): void {
2111
+ const status = state == "show" ? 1 : state == "flash" ? 2 : 0;
2112
+ this.begin("StatusIcon");
2113
+ write_byte(status);
2114
+ write_string(sprite);
2115
+ if (status != 0) {
2116
+ for (let i = 0; i < 3; i++) write_byte(i < color.length ? <i32>color[i] : 0);
2117
+ }
2118
+ message_end();
2119
+ }
2120
+
2121
+ /**
2122
+ * Sets the round clock at the top of the player's HUD, in seconds. Sent
2123
+ * unreliably, as the game does: a client with a lagging connection may skip
2124
+ * it.
2125
+ *
2126
+ * Pawn: `RoundTime`
2127
+ */
2128
+ roundTime(seconds: number): void {
2129
+ this.begin("RoundTime", false);
2130
+ write_short(<i32>seconds);
2131
+ message_end();
2132
+ }
2133
+
2134
+ /**
2135
+ * Hides parts of the player's HUD right now. Setting `player.hideHud` does
2136
+ * the same a frame later; this is for when that is too late.
2137
+ *
2138
+ * Pawn: `HideWeapon`
2139
+ */
2140
+ hideHud(parts: HideHud[]): void {
2141
+ this.begin("HideWeapon");
2142
+ write_byte(HIDE_HUD.maskOf(parts));
2143
+ message_end();
2144
+ }
2145
+
2146
+ /**
2147
+ * Shows or hides Counter-Strike's own crosshair on the player's screen.
2148
+ *
2149
+ * Pawn: `Crosshair`
2150
+ */
2151
+ crosshair(shown: boolean): void {
2152
+ this.begin("Crosshair");
2153
+ write_byte(shown ? 1 : 0);
2154
+ message_end();
2155
+ }
2156
+
2157
+ /**
2158
+ * Sets the flashlight icon on the player's HUD: on or off, and the battery in
2159
+ * percent.
2160
+ *
2161
+ * Pawn: `Flashlight`
2162
+ */
2163
+ flashlight(on: boolean, battery: number = 100): void {
2164
+ this.begin("Flashlight");
2165
+ write_byte(on ? 1 : 0);
2166
+ write_byte(<i32>battery);
2167
+ message_end();
2168
+ }
2169
+
2170
+ /**
2171
+ * Shows the progress bar in the middle of the player's screen, filling up
2172
+ * over `seconds`; `0` hides it.
2173
+ *
2174
+ * Pawn: `BarTime`, `rg_send_bartime`
2175
+ */
2176
+ progressBar(seconds: number): void {
2177
+ this.begin("BarTime");
2178
+ write_short(fixed(seconds, 1.0));
2179
+ message_end();
2180
+ }
2181
+ }
2182
+
2183
+ /**
2184
+ * The channel a sound plays on, one of `"auto"` (the default), `"weapon"`,
2185
+ * `"voice"`, `"item"`, `"body"`, `"stream"`, `"static"`. A new sound on an
2186
+ * entity's channel cuts the one playing there; `"auto"` never cuts.
2187
+ */
2188
+ export type SoundChannel = "auto" | "weapon" | "voice" | "item" | "body" | "stream" | "static";
2189
+
2190
+ /** The options of `entity.emitSound`; every one has a default. */
2191
+ export interface SoundOptions {
2192
+ /** The entity's channel the sound plays on; `"auto"` by default. */
2193
+ channel?: SoundChannel;
2194
+ /** The volume, `0` to `1`; `1` by default. */
2195
+ volume?: number;
2196
+ /** The sound's fall-off with distance: `0` is heard across the map, `0.8` (the default) as a footstep, `2` only close by. */
2197
+ attenuation?: number;
2198
+ /** The pitch in percent: `100` (the default) as recorded, `50` an octave lower, up to `255`. */
2199
+ pitch?: number;
2200
+ }
2201
+
2202
+ // A precached file's index lives in the module's store of player fields, in
2203
+ // slot 0, which no player has: the store lasts as long as the map - as a
2204
+ // precache does - and keeps what it holds through amxts_reload, when a
2205
+ // plugin comes back after the map has loaded and can precache nothing.
2206
+ // @ts-ignore: decorator
2207
+ @external("env", "player_data_get") declare function _storedIndex(slot: i32, key: string): f64;
2208
+ // @ts-ignore: decorator
2209
+ @external("env", "player_data_set") declare function _storeIndex(slot: i32, key: string, index: f64): void;
2210
+
2211
+ /**
2212
+ * A file the game has precached - a sprite, a model, a sound - as
2213
+ * `server.precache` returns it. An effect takes it where it draws a sprite or
2214
+ * a model:
2215
+ *
2216
+ * ```ts
2217
+ * const shock = server.precache("sprites/shockwave.spr");
2218
+ * effects.beamCylinder({ at: here, radius: 385, sprite: shock, life: 0.4, width: 60 });
2219
+ * ```
2220
+ */
2221
+ export class Resource {
2222
+ constructor(
2223
+ /** The file's path, as `server.precache` was given it, e.g. `"sprites/shockwave.spr"`. */
2224
+ public readonly path: string
2225
+ ) {}
2226
+
2227
+ /**
2228
+ * The file's index in the game's precache list, as a native takes it; `0`
2229
+ * while it is not precached.
2230
+ */
2231
+ get index(): number {
2232
+ return _storedIndex(0, `precache:${this.path}`);
2233
+ }
2234
+ }
2235
+
2236
+ // Files asked for before the map loads - at the top level of a plugin - and
2237
+ // precached when it does.
2238
+ const waitingPrecaches: string[] = [];
2239
+ let precacheOpen = false;
2240
+
2241
+ function precacheNow(path: string): void {
2242
+ const lower = path.toLowerCase();
2243
+ const index = lower.endsWith(".wav") ? precache_sound(path)
2244
+ : lower.endsWith(".mdl") || lower.endsWith(".spr") ? precache_model(path)
2245
+ : precache_generic(path);
2246
+ _storeIndex(0, `precache:${path}`, index);
2247
+ }
2248
+
2249
+ function precacheWaiting(event: PluginPrecacheEvent): void {
2250
+ precacheOpen = true;
2251
+ for (let i = 0; i < waitingPrecaches.length; i++) precacheNow(waitingPrecaches[i]);
2252
+ waitingPrecaches.length = 0;
2253
+ }
2254
+ addServerListener<PluginPrecacheEvent>(precacheWaiting);
2255
+
2256
+ // ScreenFade's flags (hlsdk shake.h), which no include carries.
2257
+ const FFADE_IN = 0x0000;
2258
+ const FFADE_OUT = 0x0001;
2259
+ const FFADE_MODULATE = 0x0002;
2260
+ const FFADE_STAYOUT = 0x0004;
2261
+
2262
+ // ---------------------------------------------------------------- client messages
2263
+
2264
+ /**
2265
+ * The arguments of a message, by their place, `0` for the first: a number
2266
+ * or a text, as the message wrote it.
2267
+ *
2268
+ * ```ts
2269
+ * server.addEventListener("message:TeamScore", (event) => {
2270
+ * console.log(`${event.args.text(0)} ${event.args.number(1)}`);
2271
+ * });
2272
+ * ```
2273
+ *
2274
+ * Pawn: `get_msg_args`, `get_msg_arg_*`, `set_msg_arg_*`
2275
+ */
2276
+ export class MessageArgs {
2277
+ /** The number of arguments. */
2278
+ get length(): number {
2279
+ return get_msg_args();
2280
+ }
2281
+
2282
+ /** Whether the argument at `index` is text; otherwise it is a number. */
2283
+ isText(index: number): boolean {
2284
+ return get_msg_argtype(<i32>index + 1) == ARG_STRING;
2285
+ }
2286
+
2287
+ /** The argument at `index` as a number: a byte, a short, a coordinate, an angle. */
2288
+ number(index: number): number {
2289
+ const arg = <i32>index + 1;
2290
+ const type = get_msg_argtype(arg);
2291
+ return type == ARG_COORD || type == ARG_ANGLE ? get_msg_arg_float(arg) : get_msg_arg_int(arg);
2292
+ }
2293
+
2294
+ /** The argument at `index` as text. */
2295
+ text(index: number): string {
2296
+ return get_msg_arg_string(<i32>index + 1);
2297
+ }
2298
+
2299
+ /** Writes a number argument: the message goes out with it. */
2300
+ setNumber(index: number, value: number): void {
2301
+ const arg = <i32>index + 1;
2302
+ const type = get_msg_argtype(arg);
2303
+ if (type == ARG_COORD || type == ARG_ANGLE) set_msg_arg_float(arg, type, value);
2304
+ else set_msg_arg_int(arg, type, value);
2305
+ }
2306
+
2307
+ /** Writes a text argument: the message goes out with it. */
2308
+ setText(index: number, value: string): void {
2309
+ set_msg_arg_string(<i32>index + 1, value);
2310
+ }
2311
+ }
2312
+
2313
+ /**
2314
+ * A message the server sends its clients - a chat line, the round clock, a
2315
+ * HUD icon - heard on its way, before it leaves:
2316
+ *
2317
+ * ```ts
2318
+ * server.addEventListener("message:TextMsg", (event) => {
2319
+ * if (event.text == "#Round_Draw") event.preventDefault();
2320
+ * });
2321
+ * ```
2322
+ *
2323
+ * A message the editor knows has typed fields (`event.text`); every one has
2324
+ * its arguments in `event.args`. Writing a field changes what the client
2325
+ * gets.
2326
+ *
2327
+ * Pawn: `register_message`
2328
+ */
2329
+ export class ClientMessage {
2330
+ /** @hidden What a message's event is told apart by, at compile time. */
2331
+ __message: bool = true;
2332
+ /** @hidden The player it goes to: the message's msg_entity. */
2333
+ __receiver: i32 = 0;
2334
+
2335
+ /** The message's name, e.g. `"TextMsg"`. */
2336
+ name: string = "";
2337
+
2338
+ /** The player the message goes to; `null` for a message to everyone. */
2339
+ get player(): Player | null {
2340
+ return this.__receiver >= 1 && this.__receiver <= get_maxplayers() ? new Player(this.__receiver) : null;
2341
+ }
2342
+
2343
+ /** The message's arguments, by their place: `event.args.text(1)`. */
2344
+ get args(): MessageArgs {
2345
+ return new MessageArgs();
2346
+ }
2347
+
2348
+ /**
2349
+ * Stops the message: the client does not get it.
2350
+ *
2351
+ * Pawn: `return PLUGIN_HANDLED`
2352
+ */
2353
+ preventDefault(): void {
2354
+ handled();
2355
+ }
2356
+
2357
+ /** @hidden An argument by its number, 1 for the first, as a number. */
2358
+ protected __number(arg: i32): f64 {
2359
+ return this.args.number(arg - 1);
2360
+ }
2361
+
2362
+ /** @hidden */
2363
+ protected __setNumber(arg: i32, value: f64): void {
2364
+ this.args.setNumber(arg - 1, value);
2365
+ }
2366
+
2367
+ /** @hidden */
2368
+ protected __text(arg: i32): string {
2369
+ return get_msg_arg_string(arg);
2370
+ }
2371
+
2372
+ /** @hidden */
2373
+ protected __setText(arg: i32, value: string): void {
2374
+ set_msg_arg_string(arg, value);
2375
+ }
2376
+
2377
+ /** @hidden A player's number argument; `0` or past the players is none. */
2378
+ protected __player(arg: i32): Player | null {
2379
+ const id = get_msg_arg_int(arg);
2380
+ return id >= 1 && id <= get_maxplayers() ? new Player(id) : null;
2381
+ }
2382
+ }
2383
+
2384
+ // One register_message per message name, its listeners behind it: the engine
2385
+ // hands the plugin only the messages it listens to. Registered when the
2386
+ // server is up - a message's id is known from plugin_init - and a reload
2387
+ // takes the same public back.
2388
+ class MessageChannel {
2389
+ listeners: ((event: ClientMessage) => void)[] = [];
2390
+ constructor(public name: string, public make: () => ClientMessage) {}
2391
+ }
2392
+
2393
+ const messageChannels: MessageChannel[] = [];
2394
+ const waitingMessages: MessageChannel[] = [];
2395
+
2396
+ function messageChannel(name: string): MessageChannel | null {
2397
+ for (let i = 0; i < messageChannels.length; i++) {
2398
+ if (messageChannels[i].name == name) return messageChannels[i];
2399
+ }
2400
+ return null;
2401
+ }
2402
+
2403
+ function addMessageListener<E>(name: string, listener: (event: E) => void): void {
2404
+ let channel = messageChannel(name);
2405
+
2406
+ if (channel == null) {
2407
+ channel = new MessageChannel(name, (): ClientMessage => changetype<ClientMessage>(instantiate<E>()));
2408
+ messageChannels.push(channel);
2409
+ if (serverUp) registerMessage(channel);
2410
+ else waitingMessages.push(channel);
2411
+ }
2412
+
2413
+ channel.listeners.push(changetype<(event: ClientMessage) => void>(listener));
2414
+ }
2415
+
2416
+ function removeMessageListener<E>(name: string, listener: (event: E) => void): void {
2417
+ const channel = messageChannel(name);
2418
+ if (channel == null) return;
2419
+ const at = channel.listeners.indexOf(changetype<(event: ClientMessage) => void>(listener));
2420
+ if (at >= 0) channel.listeners.splice(at, 1);
2421
+ }
2422
+
2423
+ function registerMessage(channel: MessageChannel): void {
2424
+ const id = get_user_msgid(channel.name);
2425
+
2426
+ if (id == 0) {
2427
+ console.error(`message:${channel.name} - the game has no message by that name`);
2428
+ return;
2429
+ }
2430
+
2431
+ const fired = (message: number, dest: number, receiver: number, d: number): void => messageFired(channel, <i32>receiver);
2432
+ const pub = publicFor(fired, `msg:${channel.name}`);
2433
+ if (pub.length > 0) register_message(id, pub);
2434
+ }
2435
+
2436
+ /** The public register_message calls: (message, dest, receiver). */
2437
+ function messageFired(channel: MessageChannel, receiver: i32): void {
2438
+ const event = channel.make();
2439
+ event.name = channel.name;
2440
+ event.__receiver = receiver;
2441
+ // A copy: a listener that removes itself must not make the next one skip.
2442
+ const listeners = channel.listeners.slice(0);
2443
+ for (let i = 0; i < listeners.length; i++) listeners[i](event);
2444
+ }
2445
+
2446
+ // ---------------------------------------------------------------- player fields changing
2447
+
2448
+ // @ts-ignore: decorator
2449
+ @external("env", "player_change_listen") declare function _playerChangeListen(field: string, fn: i32): void;
2450
+ // @ts-ignore: decorator
2451
+ @external("env", "player_change_get") declare function _playerChangeGet(which: i32): f64;
2452
+ // @ts-ignore: decorator
2453
+ @external("env", "player_change_get_text") declare function _playerChangeGetText(which: i32, out: usize, max: i32): i32;
2454
+
2455
+ /**
2456
+ * A field plugins added to `Player` changed on a player - written by any
2457
+ * plugin, TypeScript or Pawn:
2458
+ *
2459
+ * ```ts
2460
+ * server.addEventListener("playerchange", (event) => {
2461
+ * print(event.player, event.value ? "You are protected" : "Your spawn protection is over");
2462
+ * }, { field: "spawnProtected" });
2463
+ * ```
2464
+ *
2465
+ * With `field`, `event.value` and `event.previous` have the field's type; a
2466
+ * named listener takes `PlayerChangeEvent<"spawnProtected">`. Without it
2467
+ * every field is heard, and `event.field` says which.
2468
+ */
2469
+ export class PlayerChangeEvent<F extends string = string> {
2470
+ /** @hidden What a change's event is told apart by, at compile time. */
2471
+ __playerChange: bool = true;
2472
+ /** @hidden The player's slot. */
2473
+ __slot: i32 = 0;
2474
+ /** @hidden The value before the change, as the module keeps it: a number, or a text. */
2475
+ __previousNumber: f64 = 0;
2476
+ /** @hidden */
2477
+ __previousText: string = "";
2478
+ /** @hidden The value after it. */
2479
+ __number: f64 = 0;
2480
+ /** @hidden */
2481
+ __text: string = "";
2482
+
2483
+ /** The field that changed, e.g. `"spawnProtected"`; a member of an object field is dotted, `"glow.enabled"`. */
2484
+ field: string = "";
2485
+
2486
+ /** The player whose field changed. */
2487
+ get player(): Player {
2488
+ return new Player(this.__slot);
2489
+ }
2490
+ }
2491
+
2492
+ /** The third argument of `server.addEventListener`. */
2493
+ export interface ServerListenerOptions {
2494
+ /**
2495
+ * For `"playerchange"`: the field listened for, e.g. `"spawnProtected"`, or
2496
+ * an object field's member, `"glow.enabled"`; an object field's name
2497
+ * hears each of its members. Left out, every field.
2498
+ */
2499
+ field?: string;
2500
+ }
2501
+
2502
+ // This plugin's playerchange listeners. The module wakes the plugin only for
2503
+ // the fields they are for (player_change_listen), and the trampoline hands a
2504
+ // change to the ones it concerns, each as its own event class - the field's
2505
+ // typed one, which the build makes (scripts/player-fields.ts).
2506
+ class PlayerChangeListener {
2507
+ constructor(
2508
+ public field: string,
2509
+ public listener: (event: PlayerChangeEvent) => void,
2510
+ public make: () => PlayerChangeEvent
2511
+ ) {}
2512
+ }
2513
+
2514
+ const playerChangeListeners: PlayerChangeListener[] = [];
2515
+ const heardFields: string[] = [];
2516
+
2517
+ /** Whether a listener for `field` hears `key`: the field itself, a member of it, or "" for every one. */
2518
+ function hears(field: string, key: string): bool {
2519
+ return field.length == 0 || key == field || (key.startsWith(field) && key.charCodeAt(field.length) == 46);
2520
+ }
2521
+
2522
+ function addPlayerChangeListener<E>(field: string, listener: (event: E) => void): void {
2523
+ const make = (): PlayerChangeEvent => changetype<PlayerChangeEvent>(instantiate<E>());
2524
+ playerChangeListeners.push(new PlayerChangeListener(field, changetype<(event: PlayerChangeEvent) => void>(listener), make));
2525
+ if (heardFields.includes(field)) return;
2526
+ heardFields.push(field);
2527
+ _playerChangeListen(field, playerChanged.index);
2528
+ }
2529
+
2530
+ function removePlayerChangeListener<E>(field: string, listener: (event: E) => void): void {
2531
+ const fn = changetype<(event: PlayerChangeEvent) => void>(listener);
2532
+ for (let i = 0; i < playerChangeListeners.length; i++) {
2533
+ const one = playerChangeListeners[i];
2534
+ if (one.field != field || one.listener != fn) continue;
2535
+ playerChangeListeners.splice(i, 1);
2536
+ return;
2537
+ }
2538
+ }
2539
+
2540
+ /** The change's key (0) or its text before (1) or after (2). */
2541
+ function changeText(which: i32): string {
2542
+ const length = _playerChangeGetText(which, 0, 0);
2543
+ if (length <= 0) return "";
2544
+ const bytes = new ArrayBuffer(length);
2545
+ _playerChangeGetText(which, changetype<usize>(bytes), length);
2546
+ return String.UTF8.decode(bytes);
2547
+ }
2548
+
2549
+ /** What the module calls when a field this plugin listens for changes on player `slot`. */
2550
+ function playerChanged(slot: i32): void {
2551
+ const key = changeText(0);
2552
+ const previousNumber = _playerChangeGet(1);
2553
+ const previousText = changeText(1);
2554
+ const number = _playerChangeGet(2);
2555
+ const text = changeText(2);
2556
+
2557
+ // A copy: a listener that removes itself must not make the next one skip.
2558
+ const listeners = playerChangeListeners.slice(0);
2559
+ for (let i = 0; i < listeners.length; i++) {
2560
+ const one = listeners[i];
2561
+ if (!hears(one.field, key)) continue;
2562
+ const event = one.make();
2563
+ event.__slot = slot;
2564
+ event.field = key;
2565
+ event.__previousNumber = previousNumber;
2566
+ event.__previousText = previousText;
2567
+ event.__number = number;
2568
+ event.__text = text;
2569
+ one.listener(event);
2570
+ }
2571
+ }
2572
+
2573
+ // ---------------------------------------------------------------- cvars
2574
+
2575
+ /** The event a cvar's change listener receives: the cvar, its old and its new value. */
2576
+ export class CvarChangeEvent {
2577
+ constructor(
2578
+ /** The cvar that changed. */
2579
+ public cvar: Cvar,
2580
+ /** The cvar's value before the change, as text. */
2581
+ public oldValue: string,
2582
+ /** The cvar's new value, as text. */
2583
+ public value: string
2584
+ ) {}
2585
+ }
2586
+
2587
+ /** A cvar's change listener: `(event) => ...`, with the old and the new value in `event`. */
2588
+ export type CvarListener = (event: CvarChangeEvent) => void;
2589
+
2590
+ // Every Cvar with a change listener, for the one trampoline that hears them.
2591
+ const watchedCvars: Cvar[] = [];
2592
+
2593
+ // Cvars made before plugin_init, attached when it comes. The facade's own init
2594
+ // listener is added before any plugin's, so a plugin's init sees them ready.
2595
+ const waitingCvars: Cvar[] = [];
2596
+ let serverUp = false;
2597
+
2598
+ // A Cvar may be made from here on: plugin_natives is over.
2599
+ let cvarsReady = false;
2600
+
2601
+ function attachWaitingCvars(event: PluginInitEvent): void {
2602
+ serverUp = true;
2603
+ // No precache came - the plugin was reloaded mid-map: what it asked for
2604
+ // is found in the store, or was never precached.
2605
+ precacheOpen = false;
2606
+ waitingPrecaches.length = 0;
2607
+ makeWaitingCvars();
2608
+ for (let i = 0; i < waitingServerCommands.length; i++) registerServerCommand(waitingServerCommands[i]);
2609
+ waitingServerCommands.length = 0;
2610
+ for (let i = 0; i < waitingTouches.length; i++) registerTouch(waitingTouches[i]);
2611
+ waitingTouches.length = 0;
2612
+ for (let i = 0; i < waitingHams.length; i++) registerHam(waitingHams[i]);
2613
+ waitingHams.length = 0;
2614
+ for (let i = 0; i < waitingMessages.length; i++) registerMessage(waitingMessages[i]);
2615
+ waitingMessages.length = 0;
2616
+ }
2617
+ addServerListener<PluginInitEvent>(attachWaitingCvars);
2618
+
2619
+
2620
+ function makeWaitingCvars(): void {
2621
+ cvarsReady = true;
2622
+ for (let i = 0; i < waitingCvars.length; i++) waitingCvars[i].attach();
2623
+ waitingCvars.length = 0;
2624
+ }
2625
+
2626
+ /**
2627
+ * @hidden The start of every exported native. A Pawn plugin calls one from
2628
+ * its plugin_init at the earliest - often before the host's own - and by then
2629
+ * plugin_natives is over, so a Cvar the native makes (a register_cvar native
2630
+ * of the plugin's) is made at once rather than when this plugin's init comes.
2631
+ */
2632
+ export function __nativeCall(): void {
2633
+ if (!cvarsReady) makeWaitingCvars();
2634
+ }
2635
+
2636
+ function cvarChanged(pointer: number, oldText: number, newText: number, unused: number): void {
2637
+ for (let i = 0; i < watchedCvars.length; i++) {
2638
+ const cvar = watchedCvars[i];
2639
+ if (cvar.pointer != <i32>pointer) continue;
2640
+ cvar.dispatch(new CvarChangeEvent(cvar, argText(1), argText(2)));
2641
+ return;
2642
+ }
2643
+ }
2644
+
2645
+ /**
2646
+ * A server cvar, read and written like an input's `value`:
2647
+ *
2648
+ * ```ts
2649
+ * const freeze = new Cvar("mp_freezetime");
2650
+ * freeze.number = 5;
2651
+ * const speed = new Cvar("my_speed", "250"); // made with 250 if it does not exist
2652
+ * speed.addEventListener("change", (event) => console.log(`${event.oldValue} -> ${event.value}`));
2653
+ * ```
2654
+ *
2655
+ * `value` is the cvar's text; `number` and `boolean` read and write the same
2656
+ * cvar as a number and as an on/off switch.
2657
+ *
2658
+ * Pawn: `get_cvar_pointer`, `create_cvar`, `get_pcvar_string`, `set_pcvar_num`, `hook_cvar_change`
2659
+ */
2660
+ export class Cvar {
2661
+ /**
2662
+ * The cvar's handle in the engine; `0` when the server has no such cvar.
2663
+ *
2664
+ * Pawn: `get_cvar_pointer`
2665
+ */
2666
+ pointer: i32 = 0;
2667
+ private listeners: CvarListener[] = [];
2668
+ private hooked: bool = false;
2669
+
2670
+ constructor(
2671
+ /** The cvar's name, as the console knows it, e.g. `"mp_timelimit"`. */
2672
+ public name: string,
2673
+ private defaultValue: string | null = null
2674
+ ) {
2675
+ if (cvarsReady) this.attach();
2676
+ else waitingCvars.push(this);
2677
+ }
2678
+
2679
+ /**
2680
+ * @internal Finds or creates the cvar and starts hearing its changes. A Cvar
2681
+ * made at the top level of a plugin waits for `plugin_init`: creating a cvar
2682
+ * while plugins are still loading takes the server down.
2683
+ */
2684
+ attach(): void {
2685
+ let pointer = get_cvar_pointer(this.name);
2686
+ const fallback = this.defaultValue;
2687
+ if (pointer == 0 && fallback != null) pointer = create_cvar(this.name, fallback);
2688
+ this.pointer = pointer;
2689
+ if (this.listeners.length > 0) this.hook();
2690
+ }
2691
+
2692
+ private hook(): void {
2693
+ if (this.hooked || this.pointer == 0) return;
2694
+ this.hooked = true;
2695
+ watchedCvars.push(this);
2696
+ const pub = publicFor(cvarChanged, "cvarchange:" + this.name);
2697
+ if (pub.length > 0) hook_cvar_change(this.pointer, pub);
2698
+ }
2699
+
2700
+ /** `true` when the server has this cvar. */
2701
+ get exists(): bool { return this.pointer != 0; }
2702
+
2703
+ /** The cvar's value as text, e.g. `"250"`. */
2704
+ get value(): string { return this.pointer != 0 ? get_pcvar_string(this.pointer) : ""; }
2705
+ set value(text: string) { if (this.pointer != 0) set_pcvar_string(this.pointer, text); }
2706
+
2707
+ /** The cvar's value as a number. A whole number is written without a fraction: `"5"`, not `"5.000000"`. */
2708
+ get number(): number { return this.pointer != 0 ? get_pcvar_float(this.pointer) : 0; }
2709
+ set number(value: number) {
2710
+ if (this.pointer == 0) return;
2711
+ if (value == Math.floor(value)) set_pcvar_num(this.pointer, <i32>value);
2712
+ else set_pcvar_float(this.pointer, value);
2713
+ }
2714
+
2715
+ /** The cvar as an on/off switch: `true` for anything but `0`. Writing `true` sets `1`, `false` sets `0`. */
2716
+ get boolean(): bool { return this.pointer != 0 && get_pcvar_num(this.pointer) != 0; }
2717
+ set boolean(on: bool) { if (this.pointer != 0) set_pcvar_num(this.pointer, on ? 1 : 0); }
2718
+
2719
+ /** Calls `listener` whenever the cvar's value changes. */
2720
+ addEventListener(type: "change", listener: CvarListener): void {
2721
+ this.listeners.push(listener);
2722
+ this.hook();
2723
+ }
2724
+
2725
+ /** Stops calling a listener added with `addEventListener`. */
2726
+ removeEventListener(type: "change", listener: CvarListener): void {
2727
+ const at = this.listeners.indexOf(listener);
2728
+ if (at >= 0) this.listeners.splice(at, 1);
2729
+ }
2730
+
2731
+ /** @internal Calls the change listeners; the server does it when the cvar changes. A plugin listens with `addEventListener`. */
2732
+ dispatch(event: CvarChangeEvent): void {
2733
+ const listeners = this.listeners.slice(0);
2734
+ for (let i = 0; i < listeners.length; i++) listeners[i](event);
2735
+ }
2736
+ }
2737
+
2738
+ /**
2739
+ * The server, an event target like the DOM's: its events, commands, map and
2740
+ * the folders AMX Mod X keeps. Used through `server`:
2741
+ *
2742
+ * ```ts
2743
+ * server.addEventListener("putinserver", (event) => {
2744
+ * print(event.player, "Welcome!"); // event is a PutinserverEvent
2745
+ * });
2746
+ * server.map; // "de_dust2"
2747
+ * server.maxPlayers; // 32
2748
+ * server.command("echo hi");
2749
+ * ```
2750
+ *
2751
+ * The event's name is written out as a string: the editor completes it and
2752
+ * hands the listener the event's own type. A listener may use the variables
2753
+ * of the function it is written in - it is a closure, as in JavaScript.
2754
+ */
2755
+ export class Server {
2756
+ /**
2757
+ * Calls `listener` every time the server raises the event `type` - or,
2758
+ * for `"message:<Name>"`, every time it sends that message to a client.
2759
+ * `"playerchange"` takes the field it is for: `{ field: "spawnProtected" }`.
2760
+ */
2761
+ addEventListener<K extends keyof ServerEventMap>(type: K, listener: (event: ServerEventMap[K]) => void, options: ServerListenerOptions = {}): void {
2762
+ // @ts-ignore: a message's event is told apart by its field, at compile time
2763
+ if (isDefined(changetype<ServerEventMap[K]>(0).__message)) addMessageListener<ServerEventMap[K]>(type.slice(8), listener);
2764
+ // @ts-ignore: and so is a field's change
2765
+ else if (isDefined(changetype<ServerEventMap[K]>(0).__playerChange)) addPlayerChangeListener<ServerEventMap[K]>(options.field ?? "", listener);
2766
+ else addServerListener<ServerEventMap[K]>(listener);
2767
+ }
2768
+
2769
+ /** Stops calling a listener added with `addEventListener` - the same function and the same options. */
2770
+ removeEventListener<K extends keyof ServerEventMap>(type: K, listener: (event: ServerEventMap[K]) => void, options: ServerListenerOptions = {}): void {
2771
+ // @ts-ignore: as in addEventListener
2772
+ if (isDefined(changetype<ServerEventMap[K]>(0).__message)) removeMessageListener<ServerEventMap[K]>(type.slice(8), listener);
2773
+ // @ts-ignore: as in addEventListener
2774
+ else if (isDefined(changetype<ServerEventMap[K]>(0).__playerChange)) removePlayerChangeListener<ServerEventMap[K]>(options.field ?? "", listener);
2775
+ else removeServerListener<ServerEventMap[K]>(listener);
2776
+ }
2777
+
2778
+ /**
2779
+ * The current map's name, e.g. `"de_dust2"`.
2780
+ *
2781
+ * Pawn: `get_mapname`
2782
+ */
2783
+ get map(): string {
2784
+ return get_mapname();
2785
+ }
2786
+
2787
+ /**
2788
+ * The number of player slots on the server, e.g. `32`.
2789
+ *
2790
+ * Pawn: `get_maxplayers`
2791
+ */
2792
+ get maxPlayers(): number {
2793
+ return get_maxplayers();
2794
+ }
2795
+
2796
+ /**
2797
+ * The AMX Mod X configs folder, relative to the game folder, as `fs` takes it:
2798
+ * `addons/amxmodx/configs` unless the server moved it.
2799
+ *
2800
+ * ```ts
2801
+ * const text = fs.readFileSync(`${server.configsDir}/myplugin.ini`);
2802
+ * ```
2803
+ *
2804
+ * Pawn: `get_configsdir`
2805
+ */
2806
+ get configsDir(): string {
2807
+ const dir = get_localinfo("amxx_configsdir");
2808
+ return dir.length > 0 ? dir : "addons/amxmodx/configs";
2809
+ }
2810
+
2811
+ /**
2812
+ * The AMX Mod X folder for plugins' data files: `addons/amxmodx/data` unless the server moved it.
2813
+ *
2814
+ * Pawn: `get_datadir`
2815
+ */
2816
+ get dataDir(): string {
2817
+ const dir = get_localinfo("amxx_datadir");
2818
+ return dir.length > 0 ? dir : "addons/amxmodx/data";
2819
+ }
2820
+
2821
+ /**
2822
+ * Runs a command in the server console, as if typed there:
2823
+ * `server.command("changelevel de_dust2")`. The text goes as it is: a `%` stays a `%`.
2824
+ *
2825
+ * Pawn: `server_cmd`
2826
+ */
2827
+ command(text: string): void {
2828
+ // server_cmd takes a `...` tail, so it goes through Call; the text is an
2829
+ // argument, not the format, or a `%` in a map name would be a conversion.
2830
+ new Call(NATIVE_server_cmd).str("%s").str(text).run();
2831
+ }
2832
+
2833
+ /**
2834
+ * Adds a command players type, e.g. `"/hp"` in chat, or a name without the slash
2835
+ * in the console.
2836
+ *
2837
+ * ```ts
2838
+ * server.addCommand("/hp", (player) => print(player, `${player.health} HP`));
2839
+ * server.addCommand("/kick", kick, { access: "Kick", description: "Kick a player" });
2840
+ * ```
2841
+ *
2842
+ * `args` are the words after the command. A chat command is not repeated in
2843
+ * chat, and it does not run for a player without the `access` right.
2844
+ * `"say time"` runs when a player writes exactly `"time"` in chat.
2845
+ *
2846
+ * Pawn: `register_clcmd`
2847
+ */
2848
+ addCommand(name: string, handler: CommandHandler, options: CommandOptions = {}): void {
2849
+ const chat = name.startsWith("/") || name.startsWith("say ");
2850
+ const access = options.access;
2851
+
2852
+ commandNames.push(name.toLowerCase());
2853
+ commandHandlers.push(handler);
2854
+ commandAccess.push(access ?? "");
2855
+
2856
+ if (!chat) {
2857
+ const flags = access != null ? ACCESS.bitOf(access) : 0;
2858
+ _clcmd(name, consoleCommand.index, flags, options.description ?? "", SHAPE_WIDE);
2859
+ return;
2860
+ }
2861
+
2862
+ if (chatHooked) return;
2863
+ chatHooked = true;
2864
+ _clcmd("say", chatCommand.index, 0, "", SHAPE_WIDE);
2865
+ _clcmd("say_team", chatCommand.index, 0, "", SHAPE_WIDE);
2866
+ }
2867
+
2868
+ /**
2869
+ * Adds a command of the server console - typed there, sent over rcon or run by
2870
+ * another plugin. Players cannot use it.
2871
+ *
2872
+ * ```ts
2873
+ * server.addServerCommand("myplugin_reset", (args) => reset(args.length > 0 ? args[0] : "all"));
2874
+ * ```
2875
+ *
2876
+ * `args` are the words after the command. It may be added at the top level of
2877
+ * the file.
2878
+ *
2879
+ * Pawn: `register_srvcmd`
2880
+ */
2881
+ addServerCommand(name: string, handler: ServerCommandHandler): void {
2882
+ const lower = name.toLowerCase();
2883
+ serverCommandNames.push(lower);
2884
+ serverCommandHandlers.push(handler);
2885
+
2886
+ if (serverUp) registerServerCommand(lower);
2887
+ else waitingServerCommands.push(lower);
2888
+ }
2889
+
2890
+ /** Shows a HUD message to every player, with the same options as `player.showHud`. */
2891
+ showHud(text: string, options: HudOptions = {}): void {
2892
+ showHudTo(0, text, options);
2893
+ }
2894
+
2895
+ /**
2896
+ * Precaches a file, so the game can use it and players download it:
2897
+ * `const shock = server.precache("sprites/shockwave.spr")`. At the top level
2898
+ * of the file it is precached when the map loads; in the `"precache"` event,
2899
+ * at once. A sound is written as the game plays it, under `sound/`:
2900
+ * `"myplugin/hit.wav"`. Returns the file as a `Resource` - what an effect
2901
+ * takes for a sprite or a model.
2902
+ *
2903
+ * Pawn: `precache_model`, `precache_sound`, `precache_generic`
2904
+ */
2905
+ precache(path: string): Resource {
2906
+ const resource = new Resource(path);
2907
+ if (precacheOpen) precacheNow(path);
2908
+ else if (!serverUp) waitingPrecaches.push(path);
2909
+ else if (resource.index == 0) console.error(`"${path}" is not precached: precache it at the top level of the plugin or in the "precache" event`);
2910
+ return resource;
2911
+ }
2912
+ }
2913
+
2914
+ /** The server the plugin runs on: its events, commands and map. */
2915
+ export const server = new Server();
2916
+
2917
+ /**
2918
+ * The game's events (reapi hookchains and Ham Sandwich functions) and round
2919
+ * control, as an event target like the DOM's:
2920
+ *
2921
+ * ```ts
2922
+ * game.addEventListener("takeDamage", (event) => {
2923
+ * if (event.player.isBot) event.preventDefault();
2924
+ * });
2925
+ * game.addEventListener("canPlayerHearPlayer", (event) => event.listener.team == event.sender.team);
2926
+ * game.addEventListener("flPlayerFallDamage", (event) => event.result / 2, true);
2927
+ * ```
2928
+ *
2929
+ * The event's type follows from its name. What a listener returns is the
2930
+ * answer to the game: before the game acts it replaces what the game would
2931
+ * do, after it (`post`) it replaces the result. A listener that returns
2932
+ * nothing leaves it to the game; `event.preventDefault()` blocks without an
2933
+ * answer. A value of the wrong type is an error in the editor and in the build.
2934
+ *
2935
+ * The game rules are its fields: `game.freezePeriod`, `game.numCtWins`,
2936
+ * `game.roundWinner`.
2937
+ *
2938
+ * Pawn: `RegisterHookChain`, `RegisterHam`, `get_member_game`
2939
+ */
2940
+ export class Game extends GameFields {
2941
+ /**
2942
+ * Calls `listener` every time the game runs `type`. With `true` - or
2943
+ * `{ post: true }` - it runs after the game has acted, with the game's answer
2944
+ * in `event.result`; by default it runs before and can stop it.
2945
+ * `classname` narrows it to one class of entity, and only that class's
2946
+ * reach the plugin: `{ classname: "weapon_knife" }`. A `"touch"` listener
2947
+ * takes the classes it is about instead: `{ toucher: "player", touched: "player" }`.
2948
+ *
2949
+ * Pawn: `RegisterHookChain`, `RegisterHam`, `register_touch`
2950
+ */
2951
+ addEventListener<K extends keyof GameEventMap, R extends GameAnswerMap[K] | void | Promise<GameAnswerMap[K] | void> = void>(
2952
+ type: K, listener: (event: GameEventMap[K]) => R, options: boolean | GameListenerOptions = {}
2953
+ ): void {
2954
+ const given = options as GameListenerOptions;
2955
+ if (idof<GameEventMap[K]>() == idof<TouchEvent>()) {
2956
+ if (!isVoid<R>()) ERROR("a touch listener answers nothing: block the touch with event.preventDefault()");
2957
+ addTouchListener(changetype<TouchListener>(listener), given.toucher ?? "*", given.touched ?? "*");
2958
+ return;
2959
+ }
2960
+ addGameListener<GameEventMap[K], R>(listener, given.post == true, given.classname ?? "");
2961
+ }
2962
+
2963
+ /** Stops calling a listener added with `addEventListener` - the same function and the same options. */
2964
+ removeEventListener<K extends keyof GameEventMap, R extends GameAnswerMap[K] | void | Promise<GameAnswerMap[K] | void> = void>(
2965
+ type: K, listener: (event: GameEventMap[K]) => R, options: boolean | GameListenerOptions = {}
2966
+ ): void {
2967
+ const given = options as GameListenerOptions;
2968
+ if (idof<GameEventMap[K]>() == idof<TouchEvent>()) {
2969
+ removeTouchListener(changetype<TouchListener>(listener), given.toucher ?? "*", given.touched ?? "*");
2970
+ return;
2971
+ }
2972
+ removeGameListener<GameEventMap[K], R>(listener, given.post == true, given.classname ?? "");
2973
+ }
2974
+
2975
+ /**
2976
+ * The game's clock: seconds since the map started. Entity fields that hold a
2977
+ * moment - `nextThink`, `damageTime` - are on it:
2978
+ * `grenade.damageTime = game.time + 1`. The attack timers - a weapon's
2979
+ * `nextPrimaryAttack`, a player's `nextAttack` - count from now instead:
2980
+ * `weapon.nextPrimaryAttack = 1` is a second away.
2981
+ *
2982
+ * Pawn: `get_gametime`
2983
+ */
2984
+ get time(): number {
2985
+ return get_gametime();
2986
+ }
2987
+
2988
+ /**
2989
+ * Ends the round now:
2990
+ *
2991
+ * ```ts
2992
+ * game.endRound({ winner: "TERRORIST" }); // terrorists win, next round in 5 s
2993
+ * game.endRound({ winner: "draw", delay: 3 }); // a draw, next round in 3 s
2994
+ * game.endRound({ winner: "none", message: "" }); // a quiet restart: no message
2995
+ * ```
2996
+ *
2997
+ * The winner sets the score, the message and the sound (`"Terrorists Win!"`);
2998
+ * `message` and `sound` replace them, `""` turns them off.
2999
+ *
3000
+ * Pawn: `rg_round_end`
3001
+ */
3002
+ endRound(options: EndRoundOptions): void {
3003
+ const status = max(WINNER_NAMES.indexOf(options.winner), 0);
3004
+ rg_round_end(options.delay ?? 5.0, status, ROUND_REASONS[status], options.message ?? "default", options.sound ?? "default", options.dispatch ?? false);
3005
+ }
3006
+ }
3007
+
3008
+ /**
3009
+ * The third argument of `game.addEventListener`: `true` stands for
3010
+ * `{ post: true }`; a `"touch"` listener names the classes it is about.
3011
+ */
3012
+ export interface GameListenerOptions {
3013
+ /** Runs the listener after the game has acted, with its answer in `event.result`. */
3014
+ post?: boolean;
3015
+ /** The class of entity the event is listened for on, e.g. `"weapon_knife"`: only its entities reach the listener. */
3016
+ classname?: string;
3017
+ /** For `"touch"`: the class of the entity that moves into the other, e.g. `"player"`; left out, any. */
3018
+ toucher?: string;
3019
+ /** For `"touch"`: the class of the entity touched, e.g. `"func_door"`; left out, any. */
3020
+ touched?: string;
3021
+ }
3022
+
3023
+ /**
3024
+ * The way one entity uses another - a button pressed, a door opened - one of
3025
+ * `"off"`, `"on"`, `"set"` or `"toggle"`.
3026
+ *
3027
+ * Pawn: `USE_OFF`, `USE_ON`, `USE_SET`, `USE_TOGGLE`
3028
+ */
3029
+ export type UseType = "off" | "on" | "set" | "toggle";
3030
+
3031
+ /** The options of an entity's action such as `weapon.deploy()` or `entity.takeHealth(...)`. */
3032
+ export interface ActionOptions {
3033
+ /**
3034
+ * Whether the game's listeners run too - this plugin's and every other's,
3035
+ * Pawn ones included; `true` by default. `false` runs the game's own
3036
+ * function alone.
3037
+ *
3038
+ * Pawn: `ExecuteHamB`, `ExecuteHam`
3039
+ */
3040
+ hooks?: boolean;
3041
+ }
3042
+
3043
+ /**
3044
+ * Two entities touched: `toucher` moved into `touched`. Only the classes a
3045
+ * listener asked for reach it:
3046
+ *
3047
+ * ```ts
3048
+ * game.addEventListener("touch", onTouch, { toucher: "player", touched: "player" });
3049
+ * ```
3050
+ *
3051
+ * Pawn: `register_touch`
3052
+ */
3053
+ export class TouchEvent {
3054
+ constructor(
3055
+ /** The entity that moved into the other. */
3056
+ public toucher: Entity,
3057
+ /** The entity it touched. */
3058
+ public touched: Entity
3059
+ ) {}
3060
+
3061
+ /** Blocks the touch: the game does not act on it. */
3062
+ preventDefault(): void {
3063
+ handled();
3064
+ }
3065
+ }
3066
+
3067
+ /** A touch listener, as game.addEventListener("touch", ...) takes one. */
3068
+ type TouchListener = (event: TouchEvent) => void;
3069
+
3070
+ function touchFilter(toucher: string, touched: string): TouchFilter | null {
3071
+ for (let i = 0; i < touchFilters.length; i++) {
3072
+ const filter = touchFilters[i];
3073
+ if (filter.toucher == toucher && filter.touched == touched) return filter;
3074
+ }
3075
+ return null;
3076
+ }
3077
+
3078
+ function addTouchListener(listener: TouchListener, toucher: string, touched: string): void {
3079
+ let filter = touchFilter(toucher, touched);
3080
+
3081
+ if (filter == null) {
3082
+ filter = new TouchFilter(toucher, touched);
3083
+ touchFilters.push(filter);
3084
+ if (serverUp) registerTouch(filter);
3085
+ else waitingTouches.push(filter);
3086
+ }
3087
+
3088
+ filter.listeners.push(listener);
3089
+ }
3090
+
3091
+ function removeTouchListener(listener: TouchListener, toucher: string, touched: string): void {
3092
+ const filter = touchFilter(toucher, touched);
3093
+ if (filter == null) return;
3094
+ const at = filter.listeners.indexOf(listener);
3095
+ if (at >= 0) filter.listeners.splice(at, 1);
3096
+ }
3097
+
3098
+ /** Hands one pair of classes to the engine module; a reload takes the same public back. */
3099
+ function registerTouch(filter: TouchFilter): void {
3100
+ const fired = (touched: number, toucher: number, c: number, d: number): void => touchFired(filter, touched, toucher);
3101
+ const pub = publicFor(fired, `touch:${filter.toucher}:${filter.touched}`);
3102
+ if (pub.length > 0) register_touch(filter.touched, filter.toucher, pub);
3103
+ }
3104
+
3105
+ /** The public register_touch calls: (touched, toucher), in its order. */
3106
+ function touchFired(filter: TouchFilter, touched: number, toucher: number): void {
3107
+ const event = new TouchEvent(new Entity(toucher), new Entity(touched));
3108
+ // A copy: a listener that removes itself must not make the next one skip.
3109
+ const listeners = filter.listeners.slice(0);
3110
+ for (let i = 0; i < listeners.length; i++) listeners[i](event);
3111
+ }
3112
+
3113
+ /** The winner of a round, one of `"TERRORIST"`, `"CT"`, `"draw"`, or `"none"` - a restart without a winner. */
3114
+ export type RoundWinner = "TERRORIST" | "CT" | "draw" | "none";
3115
+
3116
+ /** The options of `game.endRound`; only `winner` is required. */
3117
+ export interface EndRoundOptions {
3118
+ /** The round's winner, one of `"TERRORIST"`, `"CT"`, `"draw"`, or `"none"` - a restart. */
3119
+ winner: RoundWinner;
3120
+ /** Seconds until the next round starts; `5` by default. */
3121
+ delay?: number;
3122
+ /** The message in the middle of the screen, or a game text such as `"#Terrorists_Win"`; `"default"` is the usual one for this winner, `""` none. */
3123
+ message?: string;
3124
+ /** The sound, a radio phrase such as `"terwin"`; `"default"` is the usual one for this winner, `""` none. */
3125
+ sound?: string;
3126
+ /**
3127
+ * `true` to tell the `roundEnd` listeners of every plugin, Pawn ones too, as when
3128
+ * the game ends a round itself. `false` by default: a `roundEnd` listener that
3129
+ * ends the round would call itself.
3130
+ *
3131
+ * Pawn: `rg_round_end(..., trigger)`
3132
+ */
3133
+ dispatch?: boolean;
3134
+ }
3135
+
3136
+ /** The game the plugin runs in: its events (reapi hookchains and Ham Sandwich functions), its rules' fields and `endRound`. */
3137
+ export const game = new Game();
3138
+
3139
+ /**
3140
+ * The places a message can show: `Variant.chat`, `center`, `console`, `notify`.
3141
+ * A plain string works the same - `"center"`; an unknown name goes to chat.
3142
+ *
3143
+ * Pawn: `print_chat`, `print_center`, `print_console`, `print_notify`
3144
+ */
3145
+ export namespace Variant {
3146
+ /** A line in the player's chat. */
3147
+ export const chat: string = "chat";
3148
+ /** Text in the middle of the player's screen. */
3149
+ export const center: string = "center";
3150
+ /** A line in the player's console. */
3151
+ export const console: string = "console";
3152
+ /** A line in the player's console, sent as a notification; with `developer 1` CS also shows it in the top-left corner of the screen. */
3153
+ export const notify: string = "notify";
3154
+ }
3155
+
3156
+ /** The place a message shows, as a string: one of `"chat"`, `"center"`, `"console"` or `"notify"`. */
3157
+ export type VariantName = "chat" | "center" | "console" | "notify";
3158
+
3159
+ // Both come from the generator, so neither can fall behind: Flag is every
3160
+ // ADMIN_* the includes declare, and Event is every forward the host plugin
3161
+ // relays. They are re-exported here so that a plugin imports one file.
3162
+ export { Flag } from "./constants";
3163
+ export * from "./events";
3164
+
3165
+ // Re-exporting does not bring a name into this file's own scope, and these
3166
+ // are used here: `Flag` for a command's default argument, the other two to
3167
+ // turn an event's short name into the forward the host plugin relays.
3168
+ import { Flag, FlagName, flagOf, HookName, hookIdOf } from "./constants";
3169
+ import { Entity, GameFields, PlayerFields } from "./entities";
3170
+ import {
3171
+ MSG_ALL, MSG_ONE, MSG_ONE_UNRELIABLE, MODEL_AUTO, m_iDeaths, m_iTeam, LibType_Library, SPEAK_MUTED, SPEAK_ALL, SPEAK_LISTENALL, CS_T_TERROR, CS_CT_URBAN, CS_DONTCHANGE,
3172
+ Ham_CS_RoundRespawn, Ham_CS_Player_ResetMaxSpeed, ARG_ANGLE, ARG_COORD, ARG_STRING
3173
+ } from "./constants";
3174
+ export { Entity, Weapon, WeaponKind, weaponKindOf } from "./entities";
3175
+ // The names an enum field takes and gives: `entity.renderMode = "additive"`.
3176
+ export {
3177
+ RenderMode, RenderFx, MoveType, Solid, TakeDamage, DeadFlag, WaterLevel, Contents, FixAngle, HitGroup,
3178
+ ArmorType, ObserverMode, JoinState, GameMenu, PlayerModel, IgnoredChat, ThrowDirection, BloodColor, MonsterState, MusicState
3179
+ } from "./entities";
3180
+ import {
3181
+ NATIVE_server_cmd, NATIVE_client_cmd, NATIVE_CreateMultiForward, NATIVE_ExecuteForward, get_gametime, get_mapname,
3182
+ nvault_open, nvault_set, nvault_remove, rg_round_end
3183
+ } from "./natives";
3184
+ import { ET_IGNORE, ET_STOP, FP_ARRAY, FP_CELL, FP_FLOAT, FP_STRING } from "./constants";
3185
+ import { ROUND_NONE, ROUND_CTS_WIN, ROUND_TERRORISTS_WIN, ROUND_END_DRAW } from "./constants";
3186
+ import { PluginInitEvent, PluginPrecacheEvent, ServerEventMap, addServerListener, removeServerListener } from "./events";
3187
+
3188
+ function variantOf(variant: VariantName): number {
3189
+ if (variant == Variant.center) return 4;
3190
+ if (variant == Variant.console) return 2;
3191
+ if (variant == Variant.notify) return 1;
3192
+ return 3;
3193
+ }
3194
+
3195
+ /** A message's recipient together with its place: `{ id: 0, variant: "center" }`. `id` is a player's `id`, `0` for everyone. */
3196
+ export interface Target {
3197
+ /** The recipient's player `id`; `0` for every player. */
3198
+ id: number;
3199
+ /** The place the message shows, one of `"chat"` (the default), `"center"`, `"console"` or `"notify"`. */
3200
+ variant?: VariantName;
3201
+ }
3202
+
3203
+ // One letter is one colour everywhere; a place draws the ones it can and
3204
+ // drops the rest.
3205
+ /** The colour tags chat draws: `!y` yellow, `!r` red, `!d` grey, `!g` green, `!b` blue, `!t` the reader's team. */
3206
+ const CHAT_TAGS = "yrdgbt";
3207
+ /** The colour tags a menu draws: `!y` yellow, `!r` red, `!d` grey, `!w` white, `!R` to the right edge. */
3208
+ const MENU_TAGS = "yrdwR";
3209
+
3210
+ /** Turns a chat line's colour tags (`!g`, `!r`, ...) into the colour codes the client reads, and records in `swapTeam` which team colour the line needs. `print` calls it; exported for tests. */
3211
+ export function paint(text: string): string {
3212
+ swapTeam = "";
3213
+
3214
+ let out = "";
3215
+
3216
+ for (let i = 0; i < text.length; i++) {
3217
+ const tag = i + 1 < text.length ? text.charAt(i + 1) : "";
3218
+
3219
+ if (text.charAt(i) != "!" || tag.length == 0 || !(CHAT_TAGS.includes(tag) || MENU_TAGS.includes(tag))) {
3220
+ // Not a tag after all: the `!` stands for itself.
3221
+ out += text.charAt(i);
3222
+ continue;
3223
+ }
3224
+
3225
+ i++;
3226
+
3227
+ // A menu's tag (`!w`, `!R`) means nothing in chat: it goes.
3228
+ if (!CHAT_TAGS.includes(tag)) continue;
3229
+
3230
+ let code: i32 = 0x03;
3231
+ let swap = "";
3232
+
3233
+ if (tag == "g") code = 0x04;
3234
+ else if (tag == "y") code = 0x01;
3235
+ else if (tag == "t") swap = SENDER_TEAM;
3236
+ else if (tag == "r") swap = "TERRORIST";
3237
+ else if (tag == "b") swap = "CT";
3238
+ else if (tag == "d") swap = "SPECTATOR";
3239
+
3240
+ out += String.fromCharCode(code);
3241
+
3242
+ // The first colour that needs a swap decides it; the rest inherit,
3243
+ // because the swap belongs to the message and not to a character.
3244
+ if (swap.length > 0 && swapTeam.length == 0) swapTeam = swap;
3245
+ }
3246
+
3247
+ return out;
3248
+ }
3249
+
3250
+ function send(id: i32, channel: i32, message: string): void {
3251
+ // Chat is the one channel with colours in it; the rest are plain text and
3252
+ // go where they always went.
3253
+ if (channel != 3) {
3254
+ _printClient(id, channel, message);
3255
+ return;
3256
+ }
3257
+
3258
+ const painted = paint(message);
3259
+ _sayText(id, painted, swapTeam);
3260
+ }
3261
+
3262
+ /** The team colour the last `paint()` chose for the line, one of `"TERRORIST"` (red), `"CT"` (blue), `"SPECTATOR"` (grey), or `""` - the reader's own team colour. `print` reads it right after. */
3263
+ export let swapTeam: string = "";
3264
+
3265
+ /** `!t` asks for the sender's own team, which the module fills in. */
3266
+ const SENDER_TEAM = "";
3267
+
3268
+ /**
3269
+ * Sends a message to a player, or to every player (`0`).
3270
+ *
3271
+ * ```ts
3272
+ * print(player, "Health restored!"); // the player's chat
3273
+ * print(0, "Round starts in 5 seconds"); // everyone's chat
3274
+ * print(player, "Health restored!", "center"); // the middle of the player's screen
3275
+ * print({ id: 0, variant: "center" }, "Go!"); // the middle of everyone's screen
3276
+ * ```
3277
+ *
3278
+ * The first argument is a player, a player's `id`, or `0` for everyone. The third
3279
+ * is where the message shows, one of `"chat"` (the default), `"center"` - the middle of
3280
+ * the screen, `"console"` - the player's console, `"notify"` - the console too; CS
3281
+ * shows it on screen only with `developer 1`.
3282
+ *
3283
+ * Colour tags work in chat only, and a letter is the same colour as in a menu:
3284
+ * - `!y` yellow (the usual chat colour), `!g` green
3285
+ * - `!r` red, `!b` blue, `!d` grey, `!t` the colour of the reader's team
3286
+ *
3287
+ * A menu's own tags (`!w`, `!R`) are dropped from a chat line. Red, blue,
3288
+ * grey and `!t` share the message's one team colour: the first one used wins.
3289
+ *
3290
+ * Pawn: `client_print`, `client_print_color`
3291
+ */
3292
+ export function print<T extends Target | Client | number = Target>(to: T, message: string, variant: VariantName = "chat"): void {
3293
+ // A player id is a number - an f64, since number is JavaScript's.
3294
+ if (isInteger<T>() || isFloat<T>()) {
3295
+ send(<i32>to, variantOf(variant), message);
3296
+ } else if (isReference<T>() && idof<T>() == idof<Client>()) {
3297
+ send(<i32>changetype<Client>(to).id, variantOf(variant), message);
3298
+ } else if (isReference<T>() && idof<T>() == idof<Player>()) {
3299
+ send(<i32>changetype<Player>(to).id, variantOf(variant), message);
3300
+ } else if (isReference<T>() && idof<T>() == idof<Target>()) {
3301
+ const target = changetype<Target>(to);
3302
+ send(<i32>target.id, variantOf(target.variant ?? "chat"), message);
3303
+ } else {
3304
+ ERROR("print takes a player, a player id or a target object");
3305
+ }
3306
+ }
3307
+
3308
+ /**
3309
+ * The server's dictionaries: the files of `data/lang`, a line per key and
3310
+ * language, and each player reads them in his own.
3311
+ *
3312
+ * ```ts
3313
+ * lang.load("myplugin"); // data/lang/myplugin.txt
3314
+ * print(player, lang.translate(player, "MYPLUGIN_WELCOME", [player.name]));
3315
+ * ```
3316
+ *
3317
+ * Pawn: `register_dictionary`, `LookupLangKey`
3318
+ */
3319
+ export namespace lang {
3320
+ /**
3321
+ * Loads the dictionary `data/lang/<name>.txt`: `lang.load("myplugin")`.
3322
+ * `false` when there is no such file.
3323
+ *
3324
+ * Pawn: `register_dictionary`
3325
+ */
3326
+ export function load(name: string): boolean {
3327
+ return register_dictionary(`${name}.txt`) != 0;
3328
+ }
3329
+
3330
+ /**
3331
+ * The key's line in the player's language, `null` for the server's:
3332
+ * `lang.translate(player, "MYPLUGIN_WELCOME", [player.name])`.
3333
+ *
3334
+ * `%s`, `%d`, `%f` (`%.1f`, `%02d`, ...) are filled from `args` in order;
3335
+ * one no argument is left for stays as written. The dictionary's colour
3336
+ * codes come back as tags - `\y` as `!y`, `^4` as `!g` - so the line goes
3337
+ * to a menu and to chat alike. A key no dictionary has comes back as it is.
3338
+ *
3339
+ * Pawn: `LookupLangKey`, `format` with `%L`
3340
+ */
3341
+ export function translate(player: Client | null, key: string, args: string[] = []): string {
3342
+ const found = lookupLang(key, player != null ? <i32>player.id : 0);
3343
+ return found.length > 0 ? formatPawn(colorTags(found), new TextCursor(args)) : key;
3344
+ }
3345
+ }
3346
+
3347
+ /**
3348
+ * Reads and sets a cvar by name in one call: `cvar.num("mp_freezetime")`.
3349
+ *
3350
+ * For a cvar used more than once `Cvar` is the better choice: it creates a
3351
+ * missing cvar, hears its changes and reads it as text, a number or a switch.
3352
+ */
3353
+ export namespace cvar {
3354
+ /**
3355
+ * The cvar's value as a whole number: `cvar.num("mp_freezetime")`.
3356
+ *
3357
+ * Pawn: `get_cvar_num`
3358
+ */
3359
+ export function num(name: string): number {
3360
+ return get_cvar_num(name);
3361
+ }
3362
+
3363
+ /**
3364
+ * Sets the cvar to a whole number: `cvar.setNum("mp_freezetime", 5)`.
3365
+ *
3366
+ * Pawn: `set_cvar_num`
3367
+ */
3368
+ export function setNum(name: string, value: number): void {
3369
+ set_cvar_num(name, value);
3370
+ }
3371
+
3372
+ /**
3373
+ * The cvar's value as text: `cvar.str("hostname")`.
3374
+ *
3375
+ * Pawn: `get_cvar_string`
3376
+ */
3377
+ export function str(name: string): string {
3378
+ return get_cvar_string(name);
3379
+ }
3380
+
3381
+ /**
3382
+ * Sets the cvar's text: `cvar.setStr("hostname", "My Server")`.
3383
+ *
3384
+ * Pawn: `set_cvar_string`
3385
+ */
3386
+ export function setStr(name: string, value: string): void {
3387
+ set_cvar_string(name, value);
3388
+ }
3389
+ }
3390
+
3391
+ /**
3392
+ * Registers a console command for players; the handler gets the `id` of the
3393
+ * player who typed it. `server.addCommand` is the usual way.
3394
+ *
3395
+ * Pawn: `register_clcmd`
3396
+ */
3397
+ export function cmd(pattern: string, handler: Handler, flag: FlagName = "ALL", info: string = ""): void {
3398
+ _clcmd(pattern, hostIndex(handler, false), flagOf(flag), info, SHAPE_NARROW);
3399
+ }
3400
+
3401
+ /**
3402
+ * Registers a console command for players whose handler gets the raw
3403
+ * arguments `(id, level, cid)`. `handled()` in the handler stops the command
3404
+ * from going on to other plugins.
3405
+ *
3406
+ * Pawn: `register_clcmd`
3407
+ */
3408
+ export function cmdWide(pattern: string, handler: WideHandler, flag: FlagName = "ALL", info: string = ""): void {
3409
+ _clcmd(pattern, hostIndex(handler, true), flagOf(flag), info, SHAPE_WIDE);
3410
+ }
3411
+
3412
+ /** The function a timer runs: `() => ...`. */
3413
+ export type TimerHandler = () => void;
3414
+
3415
+ // The timers that are armed, by handle. The host fires a task by calling one
3416
+ // function with the task's id; that function is timerFired, which calls the
3417
+ // handler from here - with its closure, which a bare table index would lose.
3418
+ class Timer {
3419
+ constructor(public handler: TimerHandler, public repeat: bool) {}
3420
+ }
3421
+
3422
+ const timers = new Map<i32, Timer>();
3423
+
3424
+ function timerFired(handle: i32): void {
3425
+ if (!timers.has(handle)) return;
3426
+ const timer = timers.get(handle);
3427
+ if (!timer.repeat) timers.delete(handle);
3428
+ timer.handler();
3429
+ }
3430
+
3431
+ function armTimer(handler: TimerHandler, ms: number, repeat: bool): i32 {
3432
+ // The host's task ids are one space for every plugin, and a timer is
3433
+ // stopped by its id: the handle comes from the host, so no other plugin
3434
+ // has it and clearTimeout here cannot stop a timer there.
3435
+ const handle = _uniqueId();
3436
+ timers.set(handle, new Timer(handler, repeat));
3437
+ // The delay crosses as the bit pattern of a 32-bit float, because a Pawn
3438
+ // native takes cells and every signature in the table is all-i on purpose.
3439
+ _task(floatCell(ms / 1000.0), timerFired.index, handle, repeat ? 1 : 0);
3440
+ return handle;
3441
+ }
3442
+
3443
+ /**
3444
+ * Runs `handler` once after `ms` milliseconds and returns the timer's handle,
3445
+ * as in the browser:
3446
+ *
3447
+ * ```ts
3448
+ * const handle = setTimeout(() => print(player, "Welcome!"), 2000);
3449
+ * clearTimeout(handle);
3450
+ * ```
3451
+ *
3452
+ * The handler may use the variables around it. The precision is one server frame.
3453
+ *
3454
+ * Pawn: `set_task`
3455
+ */
3456
+ export function setTimeout(handler: TimerHandler, ms: number = 0): number {
3457
+ return armTimer(handler, ms, false);
3458
+ }
3459
+
3460
+ /** The options of `sleep()`. */
3461
+ export interface SleepOptions {
3462
+ /** An AbortSignal that cancels the wait: the promise then rejects with the signal's reason. */
3463
+ signal?: AbortSignal;
3464
+ }
3465
+
3466
+ /**
3467
+ * Returns a promise fulfilled after `ms` milliseconds - the way to wait inside
3468
+ * an async function:
3469
+ *
3470
+ * ```ts
3471
+ * await sleep(1000);
3472
+ * await sleep(5000, { signal: AbortSignal.timeout(2000) }); // rejects after 2 s
3473
+ * ```
3474
+ *
3475
+ * The precision is one server frame. Inside an async command handler or
3476
+ * player event the wait also ends when that player leaves.
3477
+ *
3478
+ * Pawn: `set_task`
3479
+ */
3480
+ export function sleep(ms: number, options: SleepOptions = {}): Promise<void> {
3481
+ return __co_sleep(ms, options.signal ?? null);
3482
+ }
3483
+
3484
+ /**
3485
+ * Runs `handler` every `ms` milliseconds until `clearInterval` stops it;
3486
+ * returns the timer's handle.
3487
+ *
3488
+ * Pawn: `set_task` with the "b" flag
3489
+ */
3490
+ export function setInterval(handler: TimerHandler, ms: number): number {
3491
+ return armTimer(handler, ms, true);
3492
+ }
3493
+
3494
+ /**
3495
+ * Stops the timer with this handle. A timer that has already fired or been
3496
+ * stopped is ignored. Only this stops these timers: Pawn's task natives do not
3497
+ * see them.
3498
+ *
3499
+ * Pawn: `remove_task`
3500
+ */
3501
+ export function clearTimeout(handle: number): void {
3502
+ if (!timers.has(<i32>handle)) return;
3503
+ timers.delete(<i32>handle);
3504
+ _stopTask(<i32>handle);
3505
+ }
3506
+
3507
+ /** Stops the interval with this handle; the same as `clearTimeout`. */
3508
+ export function clearInterval(handle: number): void {
3509
+ clearTimeout(handle);
3510
+ }
3511
+
3512
+ // @ts-ignore: decorator
3513
+ @external("env", "call") declare function _call(id: i32, args: i32, mask: i32, argc: i32): i32;
3514
+
3515
+ // WebAssembly fixes an import's arity, so a native with a `...` tail cannot be
3516
+ // declared in ~/natives at all - only its id is - and the module cannot infer
3517
+ // the tail's types.
3518
+ /**
3519
+ * A call of a Pawn native with a `...` tail, built one argument at a time -
3520
+ * the low-level way. A native from `~/natives` is an ordinary function and
3521
+ * needs none of this.
3522
+ *
3523
+ * new Call(NATIVE_server_print).str("%s").str(text).run();
3524
+ *
3525
+ * Find the `...` in the native's declaration. Arguments before it go as they
3526
+ * are, with `num` or `str`. A number in the tail goes with `ref`, which passes
3527
+ * it by address, and `out(i)` reads back what the native wrote there; a string
3528
+ * goes with `str` anywhere.
3529
+ *
3530
+ * ExecuteHam(Ham:function, this, any:...) num, num, then the tail
3531
+ * SetHookChainArg(number, AType:type, any:...) num, num, then the tail
3532
+ * ExecuteForward(handle, &ret, any:...) num, ref for &ret, then tail
3533
+ *
3534
+ * A wrong kind of argument fails quietly, somewhere else.
3535
+ */
3536
+ export class Call {
3537
+ // Room for eight arguments, doubled whenever a call needs more: a native
3538
+ // read on every frame takes two or three, a forward's ExecuteForward 34.
3539
+ private args: StaticArray<i32> = new StaticArray<i32>(8);
3540
+ private mask: StaticArray<u8> = new StaticArray<u8>(8);
3541
+ private cells: StaticArray<i32> = new StaticArray<i32>(8);
3542
+ // Strings have to outlive the call, and one shared buffer would have the
3543
+ // second overwrite the first. Most calls - a field read - hold none, so
3544
+ // the list is made by the first.
3545
+ private held: StaticArray<i32>[] | null = null;
3546
+ private n: i32 = 0;
3547
+
3548
+ constructor(private id: i32) {}
3549
+
3550
+ /** Keeps an argument's cells alive until the call is done. */
3551
+ private hold(cells: StaticArray<i32>): void {
3552
+ let held = this.held;
3553
+ if (held == null) {
3554
+ held = [];
3555
+ this.held = held;
3556
+ }
3557
+ held.push(cells);
3558
+ }
3559
+
3560
+ /** Puts an argument and its kind at the next place. */
3561
+ private push(value: i32, kind: u8): Call {
3562
+ if (this.n == this.args.length) this.grow();
3563
+ unchecked(this.args[this.n] = value);
3564
+ unchecked(this.mask[this.n] = kind);
3565
+ this.n++;
3566
+ return this;
3567
+ }
3568
+
3569
+ // A `ref` argument is an address inside `cells`, so it moves with it.
3570
+ private grow(): void {
3571
+ const size = this.n * 2;
3572
+ const args = new StaticArray<i32>(size);
3573
+ const mask = new StaticArray<u8>(size);
3574
+ const cells = new StaticArray<i32>(size);
3575
+ for (let i = 0; i < this.n; i++) {
3576
+ const ref = unchecked(this.mask[i]) == REF;
3577
+ unchecked(args[i] = ref ? changetype<i32>(cells) + i * 4 : unchecked(this.args[i]));
3578
+ unchecked(mask[i] = unchecked(this.mask[i]));
3579
+ unchecked(cells[i] = unchecked(this.cells[i]));
3580
+ }
3581
+ this.args = args;
3582
+ this.mask = mask;
3583
+ this.cells = cells;
3584
+ }
3585
+
3586
+ /** Adds a number argument as it is: an entity index, a constant, a count. */
3587
+ num(value: number): Call {
3588
+ return this.push(<i32>value, 0x6e); // n
3589
+ }
3590
+
3591
+ /** Adds a fractional number argument, one the native declares as `Float:`. */
3592
+ float(value: f64): Call {
3593
+ return this.num(reinterpret<i32>(<f32>value));
3594
+ }
3595
+
3596
+ /** Adds a string argument, before the `...` or in the tail alike. */
3597
+ str(text: string): Call {
3598
+ const buffer = __cellsOf(text);
3599
+ this.hold(buffer);
3600
+ return this.push(changetype<i32>(buffer), 0x73); // s
3601
+ }
3602
+
3603
+ /** Adds an array of cells the native reads and may write into, followed by its length. */
3604
+ buffer(cells: CellBuffer, length: number): Call {
3605
+ return this.push(cells.address, 0x62).num(length); // b
3606
+ }
3607
+
3608
+ /**
3609
+ * Adds an array of cells the native writes into, in a `...` tail: its length
3610
+ * follows by address, as a tail's numbers do - `get_member(id, member,
3611
+ * dest[], len)`.
3612
+ */
3613
+ tailBuffer(cells: CellBuffer, length: number): Call {
3614
+ return this.push(cells.address, 0x62).ref(length); // b
3615
+ }
3616
+
3617
+ /** Adds a vector: three fractional numbers at one address - an origin, angles, a colour. Unlike `buffer`, no length follows it. */
3618
+ vec(x: f64, y: f64, z: f64): Call {
3619
+ const cells = new StaticArray<i32>(3);
3620
+ unchecked(cells[0] = reinterpret<i32>(<f32>x));
3621
+ unchecked(cells[1] = reinterpret<i32>(<f32>y));
3622
+ unchecked(cells[2] = reinterpret<i32>(<f32>z));
3623
+ this.hold(cells);
3624
+ return this.push(changetype<i32>(cells), 0x76); // v
3625
+ }
3626
+
3627
+ /** Adds a vector the native fills in; after `run` the result is in `cells`. */
3628
+ vecInto(cells: CellBuffer): Call {
3629
+ return this.push(cells.address, 0x76); // v
3630
+ }
3631
+
3632
+ /**
3633
+ * Adds an array to a forward's `...` tail: `ExecuteForward` gets it as
3634
+ * `PrepareArray` makes it. `floats` sends the numbers as `Float:`.
3635
+ */
3636
+ array(values: number[], floats: bool = false): Call {
3637
+ const cells = arrayCells(values, floats);
3638
+ this.hold(cells);
3639
+ return this.push(changetype<i32>(cells), 0x61); // a
3640
+ }
3641
+
3642
+ /** Adds a number passed by address, as a `...` tail argument must be; `out` reads what the native wrote into it. */
3643
+ ref(value: number): Call {
3644
+ if (this.n == this.args.length) this.grow();
3645
+ unchecked(this.cells[this.n] = <i32>value);
3646
+ return this.push(changetype<i32>(this.cells) + this.n * 4, REF);
3647
+ }
3648
+
3649
+ /** The value the native left in the `ref` argument at this position, after `run`. */
3650
+ out(index: i32): number {
3651
+ return unchecked(this.cells[index]);
3652
+ }
3653
+
3654
+ /** Calls the native with the arguments added so far and returns its result. */
3655
+ run(): number {
3656
+ return _call(this.id, changetype<i32>(this.args), changetype<i32>(this.mask), this.n);
3657
+ }
3658
+ }
3659
+
3660
+ /** A Call argument's kind: a number passed by address. */
3661
+ const REF: u8 = 0x72; // r
3662
+
3663
+ /** An array as the module reads one for a forward: its count, then its cells. */
3664
+ function arrayCells(values: number[], floats: bool): StaticArray<i32> {
3665
+ const cells = new StaticArray<i32>(values.length + 1);
3666
+ unchecked(cells[0] = values.length);
3667
+ for (let i = 0; i < values.length; i++) unchecked(cells[i + 1] = floats ? reinterpret<i32>(<f32>unchecked(values[i])) : <i32>unchecked(values[i]));
3668
+ return cells;
3669
+ }
3670
+
3671
+ // ---------------------------------------------------------------- field natives
3672
+
3673
+ // What a field of reapi's field natives - get_entvar, get_member, get_pmove -
3674
+ // holds, as the generated __<native>_kind tables of ~/natives say
3675
+ // (scripts/generate-wasm-api.ts): a whole number, a Float, a vector or text,
3676
+ // and FIELD_ELEMENT on top for an array member.
3677
+ const FIELD_FLOAT: i32 = 1;
3678
+ const FIELD_VECTOR: i32 = 2;
3679
+ const FIELD_STRING: i32 = 3;
3680
+ const FIELD_ELEMENT: i32 = 4;
3681
+
3682
+ /** The longest text a field native writes back: reapi's longest member holds 256 bytes. */
3683
+ const FIELD_TEXT: i32 = 1024;
3684
+
3685
+ /** The type a field of this kind reads as - what `T` has to be. */
3686
+ function fieldType(kind: i32): string {
3687
+ const base = kind & 3;
3688
+ return base == FIELD_VECTOR ? "Vector" : base == FIELD_STRING ? "string" : "number";
3689
+ }
3690
+
3691
+ /** Whether T is what a field of this kind reads as; says so in the console when it is not. */
3692
+ function fieldTakes<T>(kind: i32, native: string): bool {
3693
+ const base = kind & 3;
3694
+ const vector = isArray<T>() || isVector<T>();
3695
+ const fits = isString<T>() ? base == FIELD_STRING : vector ? base == FIELD_VECTOR : base < FIELD_VECTOR;
3696
+ if (!fits) console.error(`${native}: this field is a ${fieldType(kind)} - write ${native}<${fieldType(kind)}>(...)`);
3697
+ return fits;
3698
+ }
3699
+
3700
+ /**
3701
+ * @hidden A field of a reapi field native, as T: a whole number or a Float
3702
+ * as a number (or a boolean), a vector as a Vector, text as a string. A T the
3703
+ * field is not reads as nothing, and the console says which one to write.
3704
+ */
3705
+ export function __getField<T>(call: Call, kind: i32, element: number, native: string): T {
3706
+ if (isString<T>()) {
3707
+ if (!fieldTakes<T>(kind, native)) return changetype<T>("");
3708
+ const text = new CellBuffer(FIELD_TEXT);
3709
+ call.tailBuffer(text, FIELD_TEXT - 1);
3710
+ if (kind & FIELD_ELEMENT) call.ref(element);
3711
+ call.run();
3712
+ return changetype<T>(text.text());
3713
+ }
3714
+ if (isReference<T>()) {
3715
+ // A Vector, or number[] it is one of.
3716
+ if (!fieldTakes<T>(kind, native)) return changetype<T>(new Vector());
3717
+ const cells = new CellBuffer(3);
3718
+ call.vecInto(cells);
3719
+ if (kind & FIELD_ELEMENT) call.ref(element);
3720
+ call.run();
3721
+ return changetype<T>(new Vector(cells.float(0), cells.float(1), cells.float(2)));
3722
+ }
3723
+ if (!isFloat<T>() && !isInteger<T>()) ERROR("a field reads as number, boolean, string or Vector");
3724
+ if (!fieldTakes<T>(kind, native)) return <T>0;
3725
+ if (kind & FIELD_ELEMENT) call.ref(element);
3726
+ const cell = <i32>call.run();
3727
+ if (isBoolean<T>()) return <T>(cell != 0);
3728
+ return <T>((kind & 3) == FIELD_FLOAT ? cellFloat(cell) : <f64>cell);
3729
+ }
3730
+
3731
+ /**
3732
+ * @hidden Writes a field of a reapi field native from a value of its kind:
3733
+ * a number - a Float where the field is one - a boolean, a vector (a Vector or
3734
+ * any three numbers), text. A value of another kind writes nothing, and the
3735
+ * console says so. The native's result: 1 when it wrote.
3736
+ */
3737
+ export function __setField<T>(call: Call, kind: i32, value: T, element: number, native: string): number {
3738
+ if (!fieldTakes<T>(kind, native)) return 0;
3739
+ if (isString<T>()) {
3740
+ call.str(changetype<string>(value));
3741
+ } else if (isArray<T>()) {
3742
+ const vector = changetype<number[]>(value);
3743
+ call.vec(vector[0], vector[1], vector[2]);
3744
+ } else if (isBoolean<T>()) {
3745
+ call.ref(value ? 1 : 0);
3746
+ } else {
3747
+ call.ref((kind & 3) == FIELD_FLOAT ? floatCell(<f64>value) : <i32>value);
3748
+ }
3749
+ if (kind & FIELD_ELEMENT) call.ref(element);
3750
+ return call.run();
3751
+ }
3752
+
3753
+ // @ts-ignore: decorator
3754
+ @external("env", "hook") declare function _hook(id: i32, fn: i32, post: i32): i32;
3755
+ // @ts-ignore: decorator
3756
+ @external("env", "ham") declare function _ham(id: i32, entityClass: string, fn: i32, post: i32): i32;
3757
+
3758
+ /**
3759
+ * Registers a reapi hookchain with a raw handler of four numbers - the low
3760
+ * level under `game.addEventListener`, which is what a plugin uses.
3761
+ *
3762
+ * The name is reapi's, without the class where it is not needed:
3763
+ * `"restart_round"`, `"player_spawn"`; the editor completes them. The numbers
3764
+ * behind the names come from reapi's includes, so they must be the ones of the
3765
+ * reapi the server runs. Returns the hook's handle.
3766
+ *
3767
+ * Pawn: `RegisterHookChain`, `EnableHookChain`, `DisableHookChain`
3768
+ */
3769
+ export function hook(name: HookName, handler: WideHandler, post: bool = false): number {
3770
+ const id = hookIdOf(name);
3771
+
3772
+ if (id < 0) {
3773
+ console.error(`no hookchain named "${name}"`);
3774
+ return 0;
3775
+ }
3776
+
3777
+ return _hook(id, hostIndex(handler, true), post ? 1 : 0);
3778
+ }
3779
+
3780
+ // AMX Mod X has one entry per .amxx file and every plugin here shares the
3781
+ // host's, so this is the only place their names exist.
3782
+ /** The plugin's name, version, author and description, given to `plugin({ ... })`; `amxts_plugins` in the server console lists them. */
3783
+ export interface PluginInfo {
3784
+ /** The plugin's name, e.g. `"My Plugin"`. */
3785
+ name: string;
3786
+ /** The plugin's version, e.g. `"1.0.0"`. */
3787
+ version: string;
3788
+ /** The plugin's author. */
3789
+ author: string;
3790
+ /** The plugin's description, in one line. */
3791
+ description?: string;
3792
+ /**
3793
+ * The Pawn include whose natives the plugin implements, e.g. `"myplugin.inc"`, from
3794
+ * `includes/` or beside the plugin. Each exported function reaches Pawn as the
3795
+ * include declares it, and Pawn plugins use that include.
3796
+ */
3797
+ include?: string;
3798
+ }
3799
+
3800
+ /**
3801
+ * Declares the plugin: its name, version, author and description, as the
3802
+ * server's plugin list shows them. Called once, at the top level:
3803
+ *
3804
+ * ```ts
3805
+ * plugin({ name: "Hello", version: "1.0.0", author: "you", description: "An example" });
3806
+ * ```
3807
+ *
3808
+ * `include` names the Pawn include whose natives the plugin implements.
3809
+ *
3810
+ * Pawn: `register_plugin`
3811
+ */
3812
+ export function plugin(info: PluginInfo): void {
3813
+ _plugin(info.name, info.version, info.author, info.description ?? "");
3814
+ }
3815
+
3816
+ // ---------------------------------------------------------------- modules
3817
+
3818
+ /**
3819
+ * The modules' settings in `amxts.config.ts`, each under its module's `configKey`.
3820
+ * Empty here: a module adds its key by augmenting this interface in
3821
+ * `"@amxts/core"` - `menus?: Partial<MenuCoreOptions>` - and the editor checks
3822
+ * the config against it.
3823
+ */
3824
+ export interface ModuleOptions {}
3825
+
3826
+ /**
3827
+ * Defines a module: `export default defineModule<Options>({ meta, requires,
3828
+ * defaults, setup })` in its module file. `setup` runs once,
3829
+ * when the server loads the module, with `defaults` and what `amxts.config.ts`
3830
+ * sets over them. Global; `import { defineModule } from "@amxts/core"` works too.
3831
+ */
3832
+ // @ts-ignore: decorator
3833
+ @global export function defineModule<T>(definition: AmxtsModule<T>): AmxtsModule<T> {
3834
+ return definition;
3835
+ }
3836
+
3837
+ // ---------------------------------------------------------------- forwards
3838
+
3839
+ // @ts-ignore: decorator
3840
+ @external("env", "subscribe") declare function _subscribe(forward: string, fn: i32, tag: i32): void;
3841
+ // @ts-ignore: decorator
3842
+ @external("env", "emit_local") declare function _emitLocal(forward: string, mask: string, cells: i32, count: i32): void;
3843
+
3844
+ // @ts-ignore: decorator
3845
+ @external("env", "nvault_lookup") declare function _nvaultLookup(vault: i32, key: i32, value: i32, maxlen: i32, timestamp: i32): i32;
3846
+
3847
+ /**
3848
+ * The forward's stopping rule, one of: `"never"` - every plugin hears it, whatever it
3849
+ * returns; `"handled"` - the first plugin that says it handled the forward
3850
+ * stops it.
3851
+ *
3852
+ * Pawn: `ET_IGNORE`, `ET_STOP`
3853
+ */
3854
+ export type ForwardStop = "never" | "handled";
3855
+
3856
+ /** A placeholder for an unused type argument of `Forward`: `Forward<number>` has one argument. */
3857
+ export class NoArgument {}
3858
+
3859
+ function isArgument<T>(): bool {
3860
+ return !(isReference<T>() && !isString<T>() && nameof<T>() == "NoArgument");
3861
+ }
3862
+
3863
+ /**
3864
+ * How a Forward's argument crosses, as the build read it off the forward's
3865
+ * Pawn declaration or its type (scripts/plugin-natives.ts, crossForwards): one
3866
+ * letter per argument - `t` a Team as its TeamName number, `w` a RoundWinner
3867
+ * as its WinStatus number, `f` a number as a Float, `F` an array of numbers as
3868
+ * Floats, `_` (or nothing) as its type says.
3869
+ */
3870
+ const CROSS_TEAM = "t";
3871
+ const CROSS_WINNER = "w";
3872
+ const CROSS_FLOAT = "f";
3873
+ const CROSS_FLOATS = "F";
3874
+
3875
+ /** The most arguments an AMX Mod X forward takes (FORWARD_MAX_PARAMS), and so a Forward. */
3876
+ const MAX_FORWARD_ARGS: i32 = 32;
3877
+
3878
+ /** WinStatus by number: WINSTATUS_NONE 0, CTS 1, TERRORISTS 2, DRAW 3. */
3879
+ const WINNER_NAMES: RoundWinner[] = ["none", "CT", "TERRORIST", "draw"];
3880
+
3881
+ /** The reason a round ends with, by WinStatus number: rg_round_end's `event`. */
3882
+ const ROUND_REASONS: i32[] = [ROUND_NONE, ROUND_CTS_WIN, ROUND_TERRORISTS_WIN, ROUND_END_DRAW];
3883
+
3884
+ /** A name that crosses as a number - a Team or a RoundWinner - as that number. */
3885
+ function nameCell(name: string, crossing: string): i32 {
3886
+ if (crossing == CROSS_WINNER) return max(WINNER_NAMES.indexOf(name as RoundWinner), 0);
3887
+ return teamCell(name);
3888
+ }
3889
+
3890
+ /** The other way: a number a subscriber received, as the name. */
3891
+ function nameOfCell(cell: i32, crossing: string): string {
3892
+ if (crossing == CROSS_WINNER) return cell >= 0 && cell < WINNER_NAMES.length ? WINNER_NAMES[cell] : "none";
3893
+ return cell >= 0 && cell < TEAM_NAMES.length ? TEAM_NAMES[cell] : "UNASSIGNED";
3894
+ }
3895
+
3896
+ /** Whether the argument is a name that crosses as a number. */
3897
+ function crossesAsNumber(crossing: string): bool {
3898
+ return crossing == CROSS_TEAM || crossing == CROSS_WINNER;
3899
+ }
3900
+
3901
+ /** A team's TeamName number: TEAM_UNASSIGNED 0, TERRORIST 1, CT 2, SPECTATOR 3. */
3902
+ function teamCell(team: string): i32 {
3903
+ return max(TEAM_NAMES.indexOf(team as Team), 0);
3904
+ }
3905
+
3906
+ /** The crossing letter of the argument at `index`; `_` past the end. */
3907
+ function crossingAt(crossing: string, index: i32): string {
3908
+ return index < crossing.length ? crossing.charAt(index) : "_";
3909
+ }
3910
+
3911
+ /** Whether a Forward's argument of type T is a Vector: three Floats, whatever the declaration. */
3912
+ function isVector<T>(): bool {
3913
+ return isReference<T>() && !isString<T>() && nameof<T>() == "Vector";
3914
+ }
3915
+
3916
+ /** Whether an array argument of type T crosses as Floats. */
3917
+ function crossesAsFloats<T>(crossing: string): bool {
3918
+ return isVector<T>() || crossing == CROSS_FLOATS;
3919
+ }
3920
+
3921
+ /** The FP_* constant for a forward argument of type T. */
3922
+ function forwardParam<T>(crossing: string): i32 {
3923
+ // A plugin's number is an f64, but what a forward carries is a Pawn cell -
3924
+ // a round end's status and every forward like it. Declaring it Float
3925
+ // handed Pawn plugins the bits of a double where they read a whole number,
3926
+ // so a Float is only what the declaration, or the type Float, says is one.
3927
+ if (isArray<T>()) return FP_ARRAY;
3928
+ if (crossing == CROSS_FLOAT) return FP_FLOAT;
3929
+ if (crossesAsNumber(crossing)) return FP_CELL;
3930
+ if (isString<T>()) return FP_STRING;
3931
+ return FP_CELL;
3932
+ }
3933
+
3934
+ /** The value an unused argument stands at: null for a reference, 0 for the rest. */
3935
+ function zeroOf<T>(): T {
3936
+ if (isReference<T>()) return changetype<T>(0);
3937
+ return <T>0;
3938
+ }
3939
+
3940
+ /** CreateMultiForward's FP_* for the argument at `index`, when T is one. */
3941
+ function declareArgument<T>(call: Call, crossing: string, index: i32): void {
3942
+ if (isArgument<T>()) call.ref(forwardParam<T>(crossingAt(crossing, index)));
3943
+ }
3944
+
3945
+ /** A number, a boolean, a Player or a name as the cell Pawn gets. */
3946
+ function cellOf<T>(value: T, crossing: string): i32 {
3947
+ if (isString<T>()) return nameCell(changetype<string>(value), crossing);
3948
+ if (isReference<T>()) return nameof<T>() == "Player" && value ? changetype<Player>(value).id : 0;
3949
+ if (isBoolean<T>()) return value ? 1 : 0;
3950
+ if (crossing == CROSS_FLOAT) return reinterpret<i32>(<f32>value);
3951
+ return <i32>value;
3952
+ }
3953
+
3954
+ /**
3955
+ * What an emit hands the module for this server's TypeScript subscribers
3956
+ * (emit_local): a cell and a letter per argument - `n` a cell, `s` a string,
3957
+ * `a` an array as arrayCells lays it out.
3958
+ */
3959
+ class LocalArguments {
3960
+ cells: StaticArray<i32> = new StaticArray<i32>(MAX_FORWARD_ARGS);
3961
+ mask: string = "";
3962
+ count: i32 = 0;
3963
+ // The arrays have to outlive the call, as a Call's do.
3964
+ private held: StaticArray<i32>[] = [];
3965
+
3966
+ add(cell: i32, kind: string): void {
3967
+ unchecked(this.cells[this.count++] = cell);
3968
+ this.mask += kind;
3969
+ }
3970
+
3971
+ array(cells: StaticArray<i32>): void {
3972
+ this.held.push(cells);
3973
+ this.add(changetype<i32>(cells), "a");
3974
+ }
3975
+ }
3976
+
3977
+ /**
3978
+ * The argument at `index` of an emit: onto ExecuteForward's call the way it
3979
+ * wants it - a number by address, a string, an array PrepareArray makes - and
3980
+ * into what this server's TypeScript subscribers get. Each branch is decided
3981
+ * as it compiles, so only the one for T is built.
3982
+ */
3983
+ function sendArgument<T>(call: Call, local: LocalArguments, value: T, crossing: string, index: i32): void {
3984
+ if (!isArgument<T>()) return;
3985
+ const cross = crossingAt(crossing, index);
3986
+
3987
+ if (isArray<T>()) {
3988
+ // @ts-ignore: valueof is AssemblyScript's
3989
+ if (!isFloat<valueof<T>>()) ERROR("a Forward's array argument is number[] or a Vector");
3990
+ const values = changetype<number[]>(value);
3991
+ const floats = crossesAsFloats<T>(cross);
3992
+ call.array(values, floats);
3993
+ local.array(arrayCells(values, floats));
3994
+ return;
3995
+ }
3996
+ if (isString<T>() && !crossesAsNumber(cross)) {
3997
+ call.str(changetype<string>(value));
3998
+ local.add(changetype<i32>(value), "s");
3999
+ return;
4000
+ }
4001
+ const cell = cellOf<T>(value, cross);
4002
+ call.ref(cell);
4003
+ local.add(cell, "n");
4004
+ }
4005
+
4006
+ /** The argument at `index` of the forward being delivered, as T - read from the running call (the module's CallArgs). */
4007
+ function argumentOf<T>(crossing: string, index: i32): T {
4008
+ if (!isArgument<T>()) return zeroOf<T>();
4009
+ const cross = crossingAt(crossing, index);
4010
+
4011
+ if (isString<T>()) {
4012
+ if (crossesAsNumber(cross)) return changetype<T>(nameOfCell(__nativeCell(index), cross));
4013
+ return changetype<T>(__nativeString(index));
4014
+ }
4015
+ // A reference's branches are chosen as it runs, so they stay among references.
4016
+ if (isReference<T>()) {
4017
+ if (isVector<T>()) return changetype<T>(__nativeVector(index));
4018
+ if (isArray<T>()) return changetype<T>(__forwardNumbers(index, crossesAsFloats<T>(cross)));
4019
+ return nameof<T>() == "Player" ? changetype<T>(new Player(__nativeCell(index))) : changetype<T>(0);
4020
+ }
4021
+ if (isBoolean<T>()) return <T>(__nativeCell(index) != 0);
4022
+ if (isFloat<T>() && cross == CROSS_FLOAT) return <T>reinterpret<f32>(__nativeCell(index));
4023
+ return <T>__nativeCell(index);
4024
+ }
4025
+
4026
+ /** What subscribe() hangs on: a Forward, reached by its tag - see forwardTrampoline. */
4027
+ abstract class ForwardListener {
4028
+ abstract deliver(): void;
4029
+ }
4030
+
4031
+ /** Every Forward of this plugin that has subscribers; a Forward's tag is its index. */
4032
+ const subscribedForwards: ForwardListener[] = [];
4033
+
4034
+ /**
4035
+ * What the module calls for a forward someone subscribed to.
4036
+ *
4037
+ * One function for every Forward: a trampoline cannot capture the Forward it
4038
+ * belongs to, so the module hands back the tag subscribe() gave it, and the
4039
+ * Forward reads its arguments by its own type arguments.
4040
+ */
4041
+ function forwardTrampoline(tag: i32): void {
4042
+ if (tag >= 0 && tag < subscribedForwards.length) subscribedForwards[tag].deliver();
4043
+ }
4044
+
4045
+ /**
4046
+ * A forward other plugins listen to, Pawn and TypeScript alike. Its arguments
4047
+ * are its type parameters, up to 32 - as many as AMX Mod X gives a forward:
4048
+ *
4049
+ * ```ts
4050
+ * const roundStart = new Forward("myplugin_on_round_start");
4051
+ * const roundEnd = new Forward<RoundWinner>("myplugin_on_round_end");
4052
+ * const configChanged = new Forward<string, string>("myplugin_on_config_changed");
4053
+ *
4054
+ * roundStart.emit();
4055
+ * roundEnd.emit(winner);
4056
+ * configChanged.emit(id, value);
4057
+ *
4058
+ * const swapped = new Forward<Player, Player>("myplugin_on_player_swapped");
4059
+ * swapped.emit(catcher, caught); // Pawn gets their ids
4060
+ * ```
4061
+ *
4062
+ * A Pawn plugin listens with `public myplugin_on_round_end(winner)`, as usual;
4063
+ * a TypeScript plugin with `subscribe(handler)`. A number, a boolean and a
4064
+ * Player (its `id`) reach Pawn as numbers, a `Float` as a Float, a string as
4065
+ * a string, a `number[]` or a `Vector` as an array. A `Team` or a
4066
+ * `RoundWinner` goes as Pawn's number where an include declares the forward
4067
+ * with that tag; a `Team` for a forward no include declares is a build error.
4068
+ *
4069
+ * The forward is created on its first emit, or earlier by `create()` - once
4070
+ * every plugin has loaded (`plugin_cfg` or later), or those loaded after it
4071
+ * would not hear it.
4072
+ *
4073
+ * Pawn: `CreateMultiForward`, `ExecuteForward`
4074
+ */
4075
+ export class Forward<T1 = NoArgument, T2 = NoArgument, T3 = NoArgument, T4 = NoArgument, T5 = NoArgument, T6 = NoArgument, T7 = NoArgument, T8 = NoArgument, T9 = NoArgument, T10 = NoArgument, T11 = NoArgument, T12 = NoArgument, T13 = NoArgument, T14 = NoArgument, T15 = NoArgument, T16 = NoArgument, T17 = NoArgument, T18 = NoArgument, T19 = NoArgument, T20 = NoArgument, T21 = NoArgument, T22 = NoArgument, T23 = NoArgument, T24 = NoArgument, T25 = NoArgument, T26 = NoArgument, T27 = NoArgument, T28 = NoArgument, T29 = NoArgument, T30 = NoArgument, T31 = NoArgument, T32 = NoArgument> extends ForwardListener {
4076
+ /** The forward's stopping rule, one of `"never"` (the default) or `"handled"`. Set it before the first emit. */
4077
+ stopWhen: ForwardStop = "never";
4078
+
4079
+ private handle: i32 = -1;
4080
+ private tag: i32 = -1;
4081
+ private handlers: ((a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6, a7: T7, a8: T8, a9: T9, a10: T10, a11: T11, a12: T12, a13: T13, a14: T14, a15: T15, a16: T16, a17: T17, a18: T18, a19: T19, a20: T20, a21: T21, a22: T22, a23: T23, a24: T24, a25: T25, a26: T26, a27: T27, a28: T28, a29: T29, a30: T30, a31: T31, a32: T32) => void)[] = [];
4082
+
4083
+ /**
4084
+ * @param name The forward's name, as Pawn plugins hook it.
4085
+ * @param crossing @internal Written by the build from the forward's Pawn
4086
+ * declaration in an include, or its types; a plugin leaves it out.
4087
+ */
4088
+ constructor(
4089
+ /** The forward's name, as Pawn plugins listen to it. */
4090
+ public name: string,
4091
+ private crossing: string = ""
4092
+ ) {
4093
+ super();
4094
+ }
4095
+
4096
+ /**
4097
+ * Calls `handler` each time the forward is emitted - by this plugin, another
4098
+ * TypeScript plugin or a Pawn plugin:
4099
+ *
4100
+ * ```ts
4101
+ * const greeted = new Forward<string, number>("showcase_on_greeted");
4102
+ * greeted.subscribe((name, count) => console.log(`${name}: ${count}`));
4103
+ * ```
4104
+ *
4105
+ * The handler's parameters take the forward's types; a named function may
4106
+ * take fewer of them. A forward created by a Pawn plugin reaches TypeScript
4107
+ * only when one of the amxts host plugin's includes declares it; one emitted
4108
+ * from TypeScript reaches every subscriber.
4109
+ */
4110
+ subscribe(handler: (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6, a7: T7, a8: T8, a9: T9, a10: T10, a11: T11, a12: T12, a13: T13, a14: T14, a15: T15, a16: T16, a17: T17, a18: T18, a19: T19, a20: T20, a21: T21, a22: T22, a23: T23, a24: T24, a25: T25, a26: T26, a27: T27, a28: T28, a29: T29, a30: T30, a31: T31, a32: T32) => void): void {
4111
+ if (this.tag < 0) {
4112
+ this.tag = subscribedForwards.length;
4113
+ subscribedForwards.push(this);
4114
+ _subscribe(this.name, forwardTrampoline.index, this.tag);
4115
+ }
4116
+ this.handlers.push(handler);
4117
+ }
4118
+
4119
+ /** Stops calling a handler given to `subscribe()`. */
4120
+ unsubscribe(handler: (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6, a7: T7, a8: T8, a9: T9, a10: T10, a11: T11, a12: T12, a13: T13, a14: T14, a15: T15, a16: T16, a17: T17, a18: T18, a19: T19, a20: T20, a21: T21, a22: T22, a23: T23, a24: T24, a25: T25, a26: T26, a27: T27, a28: T28, a29: T29, a30: T30, a31: T31, a32: T32) => void): void {
4121
+ const at = this.handlers.indexOf(handler);
4122
+ if (at >= 0) this.handlers.splice(at, 1);
4123
+ }
4124
+
4125
+ /** Passes the forward's arguments to every `subscribe()` handler; the server calls it, not a plugin. */
4126
+ deliver(): void {
4127
+ const a1 = argumentOf<T1>(this.crossing, 0);
4128
+ const a2 = argumentOf<T2>(this.crossing, 1);
4129
+ const a3 = argumentOf<T3>(this.crossing, 2);
4130
+ const a4 = argumentOf<T4>(this.crossing, 3);
4131
+ const a5 = argumentOf<T5>(this.crossing, 4);
4132
+ const a6 = argumentOf<T6>(this.crossing, 5);
4133
+ const a7 = argumentOf<T7>(this.crossing, 6);
4134
+ const a8 = argumentOf<T8>(this.crossing, 7);
4135
+ const a9 = argumentOf<T9>(this.crossing, 8);
4136
+ const a10 = argumentOf<T10>(this.crossing, 9);
4137
+ const a11 = argumentOf<T11>(this.crossing, 10);
4138
+ const a12 = argumentOf<T12>(this.crossing, 11);
4139
+ const a13 = argumentOf<T13>(this.crossing, 12);
4140
+ const a14 = argumentOf<T14>(this.crossing, 13);
4141
+ const a15 = argumentOf<T15>(this.crossing, 14);
4142
+ const a16 = argumentOf<T16>(this.crossing, 15);
4143
+ const a17 = argumentOf<T17>(this.crossing, 16);
4144
+ const a18 = argumentOf<T18>(this.crossing, 17);
4145
+ const a19 = argumentOf<T19>(this.crossing, 18);
4146
+ const a20 = argumentOf<T20>(this.crossing, 19);
4147
+ const a21 = argumentOf<T21>(this.crossing, 20);
4148
+ const a22 = argumentOf<T22>(this.crossing, 21);
4149
+ const a23 = argumentOf<T23>(this.crossing, 22);
4150
+ const a24 = argumentOf<T24>(this.crossing, 23);
4151
+ const a25 = argumentOf<T25>(this.crossing, 24);
4152
+ const a26 = argumentOf<T26>(this.crossing, 25);
4153
+ const a27 = argumentOf<T27>(this.crossing, 26);
4154
+ const a28 = argumentOf<T28>(this.crossing, 27);
4155
+ const a29 = argumentOf<T29>(this.crossing, 28);
4156
+ const a30 = argumentOf<T30>(this.crossing, 29);
4157
+ const a31 = argumentOf<T31>(this.crossing, 30);
4158
+ const a32 = argumentOf<T32>(this.crossing, 31);
4159
+ // A copy: a handler that unsubscribes itself must not make the next one skip.
4160
+ const handlers = this.handlers.slice(0);
4161
+ for (let i = 0; i < handlers.length; i++) {
4162
+ handlers[i](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32);
4163
+ }
4164
+ }
4165
+
4166
+ /**
4167
+ * Creates the forward now rather than on its first emit; later calls do nothing.
4168
+ *
4169
+ * Pawn: `CreateMultiForward`
4170
+ */
4171
+ create(): void {
4172
+ if (this.handle >= 0) return;
4173
+
4174
+ const call = new Call(NATIVE_CreateMultiForward)
4175
+ .str(this.name)
4176
+ .num(this.stopWhen == "handled" ? ET_STOP : ET_IGNORE);
4177
+ declareArgument<T1>(call, this.crossing, 0);
4178
+ declareArgument<T2>(call, this.crossing, 1);
4179
+ declareArgument<T3>(call, this.crossing, 2);
4180
+ declareArgument<T4>(call, this.crossing, 3);
4181
+ declareArgument<T5>(call, this.crossing, 4);
4182
+ declareArgument<T6>(call, this.crossing, 5);
4183
+ declareArgument<T7>(call, this.crossing, 6);
4184
+ declareArgument<T8>(call, this.crossing, 7);
4185
+ declareArgument<T9>(call, this.crossing, 8);
4186
+ declareArgument<T10>(call, this.crossing, 9);
4187
+ declareArgument<T11>(call, this.crossing, 10);
4188
+ declareArgument<T12>(call, this.crossing, 11);
4189
+ declareArgument<T13>(call, this.crossing, 12);
4190
+ declareArgument<T14>(call, this.crossing, 13);
4191
+ declareArgument<T15>(call, this.crossing, 14);
4192
+ declareArgument<T16>(call, this.crossing, 15);
4193
+ declareArgument<T17>(call, this.crossing, 16);
4194
+ declareArgument<T18>(call, this.crossing, 17);
4195
+ declareArgument<T19>(call, this.crossing, 18);
4196
+ declareArgument<T20>(call, this.crossing, 19);
4197
+ declareArgument<T21>(call, this.crossing, 20);
4198
+ declareArgument<T22>(call, this.crossing, 21);
4199
+ declareArgument<T23>(call, this.crossing, 22);
4200
+ declareArgument<T24>(call, this.crossing, 23);
4201
+ declareArgument<T25>(call, this.crossing, 24);
4202
+ declareArgument<T26>(call, this.crossing, 25);
4203
+ declareArgument<T27>(call, this.crossing, 26);
4204
+ declareArgument<T28>(call, this.crossing, 27);
4205
+ declareArgument<T29>(call, this.crossing, 28);
4206
+ declareArgument<T30>(call, this.crossing, 29);
4207
+ declareArgument<T31>(call, this.crossing, 30);
4208
+ declareArgument<T32>(call, this.crossing, 31);
4209
+ this.handle = call.run();
4210
+ }
4211
+
4212
+ /**
4213
+ * Sends the forward to every plugin that listens to it; `true` if it went out.
4214
+ *
4215
+ * Pawn: `ExecuteForward`
4216
+ */
4217
+ emit(
4218
+ a1: T1 = zeroOf<T1>(),
4219
+ a2: T2 = zeroOf<T2>(),
4220
+ a3: T3 = zeroOf<T3>(),
4221
+ a4: T4 = zeroOf<T4>(),
4222
+ a5: T5 = zeroOf<T5>(),
4223
+ a6: T6 = zeroOf<T6>(),
4224
+ a7: T7 = zeroOf<T7>(),
4225
+ a8: T8 = zeroOf<T8>(),
4226
+ a9: T9 = zeroOf<T9>(),
4227
+ a10: T10 = zeroOf<T10>(),
4228
+ a11: T11 = zeroOf<T11>(),
4229
+ a12: T12 = zeroOf<T12>(),
4230
+ a13: T13 = zeroOf<T13>(),
4231
+ a14: T14 = zeroOf<T14>(),
4232
+ a15: T15 = zeroOf<T15>(),
4233
+ a16: T16 = zeroOf<T16>(),
4234
+ a17: T17 = zeroOf<T17>(),
4235
+ a18: T18 = zeroOf<T18>(),
4236
+ a19: T19 = zeroOf<T19>(),
4237
+ a20: T20 = zeroOf<T20>(),
4238
+ a21: T21 = zeroOf<T21>(),
4239
+ a22: T22 = zeroOf<T22>(),
4240
+ a23: T23 = zeroOf<T23>(),
4241
+ a24: T24 = zeroOf<T24>(),
4242
+ a25: T25 = zeroOf<T25>(),
4243
+ a26: T26 = zeroOf<T26>(),
4244
+ a27: T27 = zeroOf<T27>(),
4245
+ a28: T28 = zeroOf<T28>(),
4246
+ a29: T29 = zeroOf<T29>(),
4247
+ a30: T30 = zeroOf<T30>(),
4248
+ a31: T31 = zeroOf<T31>(),
4249
+ a32: T32 = zeroOf<T32>()
4250
+ ): boolean {
4251
+ this.create();
4252
+ if (this.handle < 0) return false;
4253
+
4254
+ const call = new Call(NATIVE_ExecuteForward).num(this.handle).ref(0);
4255
+ const local = new LocalArguments();
4256
+ sendArgument<T1>(call, local, a1, this.crossing, 0);
4257
+ sendArgument<T2>(call, local, a2, this.crossing, 1);
4258
+ sendArgument<T3>(call, local, a3, this.crossing, 2);
4259
+ sendArgument<T4>(call, local, a4, this.crossing, 3);
4260
+ sendArgument<T5>(call, local, a5, this.crossing, 4);
4261
+ sendArgument<T6>(call, local, a6, this.crossing, 5);
4262
+ sendArgument<T7>(call, local, a7, this.crossing, 6);
4263
+ sendArgument<T8>(call, local, a8, this.crossing, 7);
4264
+ sendArgument<T9>(call, local, a9, this.crossing, 8);
4265
+ sendArgument<T10>(call, local, a10, this.crossing, 9);
4266
+ sendArgument<T11>(call, local, a11, this.crossing, 10);
4267
+ sendArgument<T12>(call, local, a12, this.crossing, 11);
4268
+ sendArgument<T13>(call, local, a13, this.crossing, 12);
4269
+ sendArgument<T14>(call, local, a14, this.crossing, 13);
4270
+ sendArgument<T15>(call, local, a15, this.crossing, 14);
4271
+ sendArgument<T16>(call, local, a16, this.crossing, 15);
4272
+ sendArgument<T17>(call, local, a17, this.crossing, 16);
4273
+ sendArgument<T18>(call, local, a18, this.crossing, 17);
4274
+ sendArgument<T19>(call, local, a19, this.crossing, 18);
4275
+ sendArgument<T20>(call, local, a20, this.crossing, 19);
4276
+ sendArgument<T21>(call, local, a21, this.crossing, 20);
4277
+ sendArgument<T22>(call, local, a22, this.crossing, 21);
4278
+ sendArgument<T23>(call, local, a23, this.crossing, 22);
4279
+ sendArgument<T24>(call, local, a24, this.crossing, 23);
4280
+ sendArgument<T25>(call, local, a25, this.crossing, 24);
4281
+ sendArgument<T26>(call, local, a26, this.crossing, 25);
4282
+ sendArgument<T27>(call, local, a27, this.crossing, 26);
4283
+ sendArgument<T28>(call, local, a28, this.crossing, 27);
4284
+ sendArgument<T29>(call, local, a29, this.crossing, 28);
4285
+ sendArgument<T30>(call, local, a30, this.crossing, 29);
4286
+ sendArgument<T31>(call, local, a31, this.crossing, 30);
4287
+ sendArgument<T32>(call, local, a32, this.crossing, 31);
4288
+ const sent = call.run() != 0;
4289
+
4290
+ // TypeScript subscribers, which AMX Mod X reaches only through a host
4291
+ // public - the module skips the forwards it has one for.
4292
+ _emitLocal(this.name, local.mask, changetype<i32>(local.cells), local.count);
4293
+ return sent;
4294
+ }
4295
+ }
4296
+
4297
+ // ---------------------------------------------------------------- storage
4298
+
4299
+ /**
4300
+ * A key-to-text store on disk: a Map that survives a map change and a server
4301
+ * restart.
4302
+ *
4303
+ * ```ts
4304
+ * const demos = new Storage("core_demo_counters");
4305
+ * const last = demos.get(auth); // string | null
4306
+ * demos.set(auth, "3");
4307
+ * if (demos.has(auth)) ...
4308
+ * demos.delete(auth);
4309
+ * ```
4310
+ *
4311
+ * The file is named after the storage, kept in the `vault` folder of the AMX
4312
+ * Mod X data folder, and opened on first use. Values are text: a number goes
4313
+ * in with `toString()` and comes out with `parseInt`.
4314
+ *
4315
+ * Pawn: `nvault_open`, `nvault_get`, `nvault_set`, `nvault_remove`
4316
+ */
4317
+ export class Storage {
4318
+ private vault: i32 = -1;
4319
+
4320
+ constructor(
4321
+ /** The storage's name, which is also its file's name. */
4322
+ public name: string
4323
+ ) {}
4324
+
4325
+ /** The value under `key`, or `null` when there is none. */
4326
+ get(key: string): string | null {
4327
+ const vault = this.open();
4328
+ if (vault < 0) return null;
4329
+
4330
+ const value = new CellBuffer(TEXT_MAX + 1);
4331
+ const stamp = new CellBuffer(1);
4332
+
4333
+ // nvault_lookup rather than nvault_get: the length of nvault_get's
4334
+ // buffer rides in its `...` tail by address, which the dispatcher's
4335
+ // buffer argument cannot say; nvault_lookup takes it as a plain
4336
+ // argument, and answers whether the key exists besides.
4337
+ const found = _nvaultLookup(vault, changetype<i32>(__cellsOf(key)), value.address, TEXT_MAX, stamp.address);
4338
+ return found != 0 ? value.text() : null;
4339
+ }
4340
+
4341
+ /** Puts `value` under `key`, replacing what was there. */
4342
+ set(key: string, value: string): void {
4343
+ const vault = this.open();
4344
+ if (vault >= 0) nvault_set(vault, key, value);
4345
+ }
4346
+
4347
+ /** `true` when there is a value under `key`. */
4348
+ has(key: string): boolean {
4349
+ return this.get(key) != null;
4350
+ }
4351
+
4352
+ /** Removes `key` and its value; a key that is not there is ignored. */
4353
+ delete(key: string): void {
4354
+ const vault = this.open();
4355
+ if (vault >= 0) nvault_remove(vault, key);
4356
+ }
4357
+
4358
+ private open(): i32 {
4359
+ if (this.vault < 0) this.vault = nvault_open(this.name);
4360
+ return this.vault;
4361
+ }
4362
+ }
4363
+
4364
+ // Flag enums and the array that stands for a mask - generated, see scripts/generate-flags.ts.
4365
+ export * from "./flags";
4366
+ import { Access, ACCESS, HIDE_HUD, HideHud } from "./flags";
4367
+
4368
+ // Hookchain events and game.addEventListener's hood - generated, see scripts/generate-hooks.ts.
4369
+ export * from "./hooks";
4370
+ import { GameAnswerMap, GameEventMap, addGameListener, removeGameListener } from "./hooks";
4371
+
4372
+ // Three numbers with vector math - what entity.origin and the rest return.
4373
+ export * from "./vector";
4374
+ export * from "./effects";
4375
+ export { EntityFilter } from "./entities";
4376
+
4377
+ // =============================================================================
4378
+ // What modules call through the kit (as/kit.ts): another plugin's public by
4379
+ // name, AMX Mod X `Array:` rows, and an old-style menu. Hood, like the rest of
4380
+ // this file.
4381
+ // =============================================================================
4382
+
4383
+ // get_user_msgid, message_begin, message_end, write_byte and write_short are
4384
+ // imported at the top of the file.
4385
+ import {
4386
+ callfunc_begin_i, callfunc_push_int, get_func_id, show_menu, write_string, ArrayGetArray, ArrayDestroy
4387
+ } from "./natives";
4388
+
4389
+ /**
4390
+ * A public function of another plugin - a Pawn plugin's public, or a
4391
+ * TypeScript plugin's `publicFor` name - to call from here:
4392
+ *
4393
+ * ```ts
4394
+ * const fn = PawnFunction.find(caller(), "OnAction"); // null when there is none
4395
+ * if (fn != null) fn.call().int(id).text("KEY").run();
4396
+ * const call = fn.call().int(id).int(target).buffer(256).int(255);
4397
+ * call.run();
4398
+ * const value = call.bufferText; // what it wrote into value[]
4399
+ * ```
4400
+ *
4401
+ * Pawn: `get_func_id`, `callfunc_begin_i`
4402
+ */
4403
+ export class PawnFunction {
4404
+ constructor(
4405
+ /** The `id` of the plugin the function belongs to. */
4406
+ readonly plugin: i32,
4407
+ /**
4408
+ * The function's index in its plugin.
4409
+ *
4410
+ * Pawn: `get_func_id`
4411
+ */
4412
+ readonly index: i32
4413
+ ) {}
4414
+
4415
+ /**
4416
+ * Finds the public `name` in the plugin with this `id` (a native's `caller()`);
4417
+ * `null` when the plugin has no such public.
4418
+ *
4419
+ * Pawn: `get_func_id`
4420
+ */
4421
+ static find(plugin: number, name: string): PawnFunction | null {
4422
+ const index = get_func_id(name, plugin);
4423
+ return index >= 0 ? new PawnFunction(<i32>plugin, <i32>index) : null;
4424
+ }
4425
+
4426
+ /** Starts a call of the function: add its arguments in order, then `run()`. */
4427
+ call(): PawnCall {
4428
+ return new PawnCall(this);
4429
+ }
4430
+ }
4431
+
4432
+ const PAWN_INT: u8 = 0;
4433
+ const PAWN_TEXT: u8 = 1;
4434
+ const PAWN_BUFFER: u8 = 2;
4435
+
4436
+ // @ts-ignore: decorator
4437
+ @external("env", "callfunc_text") declare function _callfuncText(text: string): i32;
4438
+ // @ts-ignore: decorator
4439
+ @external("env", "callfunc_buffer") declare function _callfuncBuffer(cells: usize, count: i32): i32;
4440
+ // @ts-ignore: decorator
4441
+ @external("env", "callfunc_finish") declare function _callfuncFinish(): i32;
4442
+
4443
+ /**
4444
+ * One call of a PawnFunction: its arguments in order, then `run()`. Any
4445
+ * number of strings and arrays may be passed, and what the function writes
4446
+ * into a `buffer()` is read afterwards from `bufferText`.
4447
+ *
4448
+ * Pawn: `callfunc_push_int`, `callfunc_push_str`, `callfunc_push_array`, `callfunc_end`
4449
+ */
4450
+ export class PawnCall {
4451
+ private kinds: u8[] = [];
4452
+ private ints: i32[] = [];
4453
+ private texts: string[] = [];
4454
+ private cells: StaticArray<i32> | null = null;
4455
+ /** The text the function wrote into its `buffer()`, once `run()` is over. */
4456
+ bufferText: string = "";
4457
+
4458
+ constructor(private fn: PawnFunction) {}
4459
+
4460
+ /** Adds a number argument. */
4461
+ int(value: number): PawnCall {
4462
+ return this.add(PAWN_INT, <i32>value, "");
4463
+ }
4464
+
4465
+ /** Adds a boolean argument; the function gets `1` or `0`. */
4466
+ bool(value: boolean): PawnCall {
4467
+ return this.add(PAWN_INT, value ? 1 : 0, "");
4468
+ }
4469
+
4470
+ /** Adds a string argument. */
4471
+ text(value: string): PawnCall {
4472
+ return this.add(PAWN_TEXT, 0, value);
4473
+ }
4474
+
4475
+ /** Adds an array of `size` cells for the function to fill - its `value[]`; one per call. The text is then in `bufferText`. */
4476
+ buffer(size: number): PawnCall {
4477
+ return this.add(PAWN_BUFFER, <i32>size, "");
4478
+ }
4479
+
4480
+ /** Calls the function and returns its result, or `0` when it could not be called. */
4481
+ run(): number {
4482
+ if (callfunc_begin_i(this.fn.index, this.fn.plugin) != 1) return 0;
4483
+
4484
+ for (let i = 0; i < this.kinds.length; i++) {
4485
+ const kind = unchecked(this.kinds[i]);
4486
+ if (kind == PAWN_INT) callfunc_push_int(unchecked(this.ints[i]));
4487
+ else if (kind == PAWN_TEXT) _callfuncText(unchecked(this.texts[i]));
4488
+ else {
4489
+ const cells = new StaticArray<i32>(unchecked(this.ints[i]));
4490
+ this.cells = cells;
4491
+ _callfuncBuffer(changetype<usize>(cells), cells.length);
4492
+ }
4493
+ }
4494
+
4495
+ const result = _callfuncFinish();
4496
+ const filled = this.cells;
4497
+ if (filled != null) this.bufferText = __cellText(changetype<usize>(filled), filled.length);
4498
+ return result;
4499
+ }
4500
+
4501
+ private add(kind: u8, value: i32, text: string): PawnCall {
4502
+ this.kinds.push(kind);
4503
+ this.ints.push(value);
4504
+ this.texts.push(text);
4505
+ return this;
4506
+ }
4507
+ }
4508
+
4509
+ /**
4510
+ * Creates an AMX Mod X `Array:` of `cellSize` cells an item and returns its handle.
4511
+ *
4512
+ * Pawn: `ArrayCreate`
4513
+ */
4514
+ export function createCellArray(cellSize: number): number {
4515
+ return _arrayCreate(<i32>cellSize, 32);
4516
+ }
4517
+
4518
+ /**
4519
+ * Frees an `Array:` made by `createCellArray`; its handle is no good afterwards.
4520
+ *
4521
+ * Pawn: `ArrayDestroy`
4522
+ */
4523
+ export function destroyCellArray(handle: number): void {
4524
+ ArrayDestroy(handle);
4525
+ }
4526
+
4527
+ /**
4528
+ * Reads every item of an `Array:` of `cellSize` cells an item, each as an array of numbers.
4529
+ *
4530
+ * Pawn: `ArrayGetArray`
4531
+ */
4532
+ export function cellArrayRows(handle: number, cellSize: number): number[][] {
4533
+ const rows: number[][] = [];
4534
+ const count = _arraySize(<i32>handle);
4535
+ const cells = new StaticArray<i32>(<i32>cellSize);
4536
+ for (let i = 0; i < count; i++) {
4537
+ ArrayGetArray(<i32>handle, i, changetype<i32>(cells), <i32>cellSize);
4538
+ const row: number[] = [];
4539
+ for (let k = 0; k < cells.length; k++) row.push(unchecked(cells[k]));
4540
+ rows.push(row);
4541
+ }
4542
+ return rows;
4543
+ }
4544
+
4545
+ /**
4546
+ * Adds one item, an array of numbers, to the end of an `Array:`.
4547
+ *
4548
+ * Pawn: `ArrayPushArray`
4549
+ */
4550
+ export function pushCellArrayRow(handle: number, row: number[]): void {
4551
+ const cells = new StaticArray<i32>(row.length);
4552
+ for (let i = 0; i < row.length; i++) unchecked(cells[i] = <i32>row[i]);
4553
+ _arrayPushArray(<i32>handle, changetype<usize>(cells), row.length);
4554
+ }
4555
+
4556
+ /** Reads a Pawn string from `count` cells of a row, from `start`: a UTF-8 byte a cell, up to the first zero. */
4557
+ export function cellsText(row: number[], start: number, count: number): string {
4558
+ const cells = new StaticArray<i32>(<i32>count + 1);
4559
+ for (let i = 0; i < <i32>count && <i32>start + i < row.length; i++) unchecked(cells[i] = <i32>row[<i32>start + i]);
4560
+ return __cellText(changetype<usize>(cells), <i32>count);
4561
+ }
4562
+
4563
+ /** Writes text as `count` cells of a Pawn string: at most `count - 1` bytes, never half a letter, the rest zeros. */
4564
+ export function textCells(text: string, count: number): number[] {
4565
+ const cells = new StaticArray<i32>(<i32>count);
4566
+ __writeCellText(text, cells);
4567
+ const row: number[] = [];
4568
+ for (let i = 0; i < cells.length; i++) row.push(unchecked(cells[i]));
4569
+ return row;
4570
+ }
4571
+
4572
+ /** Turns a menu's colour tags (`!y`, `!R`, ...) into the codes the game draws, and drops the ones only chat has (`!g`, `!b`, `!t`) and the game's own codes written into the text (`\y`); any other `!` stays. `showMenu` calls it, and a module hands its result to Pawn. */
4573
+ export function menuColors(text: string): string {
4574
+ if (!text.includes("!") && !text.includes("\\")) return text;
4575
+
4576
+ let out = "";
4577
+
4578
+ for (let i = 0; i < text.length; i++) {
4579
+ const tag = i + 1 < text.length ? text.charAt(i + 1) : "";
4580
+
4581
+ // Text is written with tags: a code is Pawn's, and a module turns Pawn's text into tags first.
4582
+ if (text.charAt(i) == "\\" && tag.length > 0 && MENU_TAGS.includes(tag)) {
4583
+ i++;
4584
+ continue;
4585
+ }
4586
+
4587
+ if (text.charAt(i) != "!" || tag.length == 0 || !(CHAT_TAGS.includes(tag) || MENU_TAGS.includes(tag))) {
4588
+ out += text.charAt(i);
4589
+ continue;
4590
+ }
4591
+
4592
+ i++;
4593
+
4594
+ // The game's own code is the same letter: `!y` is `\y`.
4595
+ if (MENU_TAGS.includes(tag)) out += "\\" + tag;
4596
+ }
4597
+
4598
+ return out;
4599
+ }
4600
+
4601
+ /**
4602
+ * Text from Pawn - a dictionary's line, a Pawn plugin's argument - with its
4603
+ * colour codes made tags: a menu's `\y` `\r` `\d` `\w` `\R` are `!y` `!r`
4604
+ * `!d` `!w` `!R`, and chat's bytes `^1` `^3` `^4` are `!y` `!t` `!g`.
4605
+ */
4606
+ export function colorTags(text: string): string {
4607
+ let out = "";
4608
+
4609
+ for (let i = 0; i < text.length; i++) {
4610
+ const c = text.charAt(i);
4611
+ const tag = i + 1 < text.length ? text.charAt(i + 1) : "";
4612
+
4613
+ if (c == "\\" && tag.length > 0 && MENU_TAGS.includes(tag)) {
4614
+ out += "!" + tag;
4615
+ i++;
4616
+ } else if (c == "\x01") out += "!y";
4617
+ else if (c == "\x03") out += "!t";
4618
+ else if (c == "\x04") out += "!g";
4619
+ else out += c;
4620
+ }
4621
+
4622
+ return out;
4623
+ }
4624
+
4625
+ /**
4626
+ * Shows a player an old-style menu of any length: `keys` are the keys it
4627
+ * accepts, `title` the name its key presses come back under.
4628
+ *
4629
+ * Colour tags, the same letters as in chat: `!y` yellow, `!r` red, `!d` grey,
4630
+ * `!w` white, `!R` to the right edge. Chat's own tags (`!g`, `!b`, `!t`)
4631
+ * are dropped, and so are the game's own codes (`\y`): text is written with tags.
4632
+ *
4633
+ * Pawn: `show_menu`, `register_menucmd`
4634
+ */
4635
+ export function showMenu(id: number, keys: number, text: string, title: string): void {
4636
+ // show_menu takes the text whole and sends it in ShowMenu messages of the
4637
+ // game's 175 bytes itself.
4638
+ show_menu(id, keys, menuColors(text), -1, title);
4639
+ }