@amxts/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/README.ru.md +162 -0
  4. package/amxmodx/base/include/amxconst.inc +594 -0
  5. package/amxmodx/base/include/amxmisc.inc +902 -0
  6. package/amxmodx/base/include/amxmodx.inc +3447 -0
  7. package/amxmodx/base/include/amxmodx_version.inc +20 -0
  8. package/amxmodx/base/include/cellarray.inc +525 -0
  9. package/amxmodx/base/include/cellstack.inc +166 -0
  10. package/amxmodx/base/include/celltrie.inc +412 -0
  11. package/amxmodx/base/include/core.inc +202 -0
  12. package/amxmodx/base/include/csstats.inc +298 -0
  13. package/amxmodx/base/include/csstats_const.inc +29 -0
  14. package/amxmodx/base/include/cstrike.inc +1287 -0
  15. package/amxmodx/base/include/cstrike_const.inc +521 -0
  16. package/amxmodx/base/include/csx.inc +259 -0
  17. package/amxmodx/base/include/cvars.inc +547 -0
  18. package/amxmodx/base/include/datapack.inc +161 -0
  19. package/amxmodx/base/include/dbi.inc +154 -0
  20. package/amxmodx/base/include/dodconst.inc +152 -0
  21. package/amxmodx/base/include/dodfun.inc +152 -0
  22. package/amxmodx/base/include/dodstats.inc +69 -0
  23. package/amxmodx/base/include/dodx.inc +161 -0
  24. package/amxmodx/base/include/engine.inc +1343 -0
  25. package/amxmodx/base/include/engine_const.inc +310 -0
  26. package/amxmodx/base/include/engine_stocks.inc +248 -0
  27. package/amxmodx/base/include/esf.inc +70 -0
  28. package/amxmodx/base/include/esf_const.inc +74 -0
  29. package/amxmodx/base/include/fakemeta.inc +1127 -0
  30. package/amxmodx/base/include/fakemeta_const.inc +793 -0
  31. package/amxmodx/base/include/fakemeta_stocks.inc +395 -0
  32. package/amxmodx/base/include/fakemeta_util.inc +882 -0
  33. package/amxmodx/base/include/file.inc +666 -0
  34. package/amxmodx/base/include/float.inc +422 -0
  35. package/amxmodx/base/include/fun.inc +333 -0
  36. package/amxmodx/base/include/gameconfig.inc +93 -0
  37. package/amxmodx/base/include/geoip.inc +264 -0
  38. package/amxmodx/base/include/ham_const.inc +4352 -0
  39. package/amxmodx/base/include/hamsandwich.inc +439 -0
  40. package/amxmodx/base/include/hlsdk_const.inc +808 -0
  41. package/amxmodx/base/include/json.inc +777 -0
  42. package/amxmodx/base/include/lang.inc +119 -0
  43. package/amxmodx/base/include/message_const.inc +1130 -0
  44. package/amxmodx/base/include/message_stocks.inc +161 -0
  45. package/amxmodx/base/include/messages.inc +604 -0
  46. package/amxmodx/base/include/newmenus.inc +387 -0
  47. package/amxmodx/base/include/ns.inc +754 -0
  48. package/amxmodx/base/include/ns_const.inc +196 -0
  49. package/amxmodx/base/include/nvault.inc +154 -0
  50. package/amxmodx/base/include/regex.inc +348 -0
  51. package/amxmodx/base/include/sockets.inc +183 -0
  52. package/amxmodx/base/include/sorting.inc +107 -0
  53. package/amxmodx/base/include/sqlx.inc +582 -0
  54. package/amxmodx/base/include/string.inc +786 -0
  55. package/amxmodx/base/include/string_const.inc +156 -0
  56. package/amxmodx/base/include/string_stocks.inc +325 -0
  57. package/amxmodx/base/include/textparse_ini.inc +214 -0
  58. package/amxmodx/base/include/textparse_smc.inc +261 -0
  59. package/amxmodx/base/include/tfcconst.inc +84 -0
  60. package/amxmodx/base/include/tfcstats.inc +62 -0
  61. package/amxmodx/base/include/tfcx.inc +148 -0
  62. package/amxmodx/base/include/time.inc +118 -0
  63. package/amxmodx/base/include/tsconst.inc +154 -0
  64. package/amxmodx/base/include/tsfun.inc +198 -0
  65. package/amxmodx/base/include/tsstats.inc +65 -0
  66. package/amxmodx/base/include/tsx.inc +88 -0
  67. package/amxmodx/base/include/vault.inc +60 -0
  68. package/amxmodx/base/include/vector.inc +131 -0
  69. package/amxmodx/base/include/xs.inc +1570 -0
  70. package/as/amxts.d.ts +153 -0
  71. package/as/constants.ts +4730 -0
  72. package/as/effects.ts +1449 -0
  73. package/as/entities.ts +5482 -0
  74. package/as/events.ts +3054 -0
  75. package/as/facade.ts +4639 -0
  76. package/as/flags.ts +299 -0
  77. package/as/fs.ts +287 -0
  78. package/as/hooks.ts +18536 -0
  79. package/as/kit.ts +25 -0
  80. package/as/lib/check.ts +86 -0
  81. package/as/modules/http.ts +184 -0
  82. package/as/natives.ts +11845 -0
  83. package/as/os.ts +30 -0
  84. package/as/promise.ts +1392 -0
  85. package/as/promise.types.d.ts +82 -0
  86. package/as/remote.ts +288 -0
  87. package/as/tsconfig.json +22 -0
  88. package/as/vector.ts +65 -0
  89. package/bin/amxts.mjs +10 -0
  90. package/includes/menu_core.inc +355 -0
  91. package/includes/order.txt +54 -0
  92. package/includes/sources.json +27 -0
  93. package/includes/universal_config.inc +355 -0
  94. package/lint/oxlint-plugin.mjs +77 -0
  95. package/node_modules/assemblyscript/LICENSE +201 -0
  96. package/node_modules/assemblyscript/NOTICE +93 -0
  97. package/node_modules/assemblyscript/package.json +63 -0
  98. package/node_modules/assemblyscript/std/README.md +6 -0
  99. package/node_modules/assemblyscript/std/assembly/array.ts +526 -0
  100. package/node_modules/assemblyscript/std/assembly/arraybuffer.ts +77 -0
  101. package/node_modules/assemblyscript/std/assembly/atomics.ts +127 -0
  102. package/node_modules/assemblyscript/std/assembly/bindings/asyncify.ts +16 -0
  103. package/node_modules/assemblyscript/std/assembly/bindings/dom.ts +291 -0
  104. package/node_modules/assemblyscript/std/assembly/bindings/node.ts +6 -0
  105. package/node_modules/assemblyscript/std/assembly/builtins.ts +2631 -0
  106. package/node_modules/assemblyscript/std/assembly/compat.ts +2 -0
  107. package/node_modules/assemblyscript/std/assembly/console.ts +42 -0
  108. package/node_modules/assemblyscript/std/assembly/crypto.ts +9 -0
  109. package/node_modules/assemblyscript/std/assembly/dataview.ts +181 -0
  110. package/node_modules/assemblyscript/std/assembly/date.ts +373 -0
  111. package/node_modules/assemblyscript/std/assembly/diagnostics.ts +11 -0
  112. package/node_modules/assemblyscript/std/assembly/error.ts +44 -0
  113. package/node_modules/assemblyscript/std/assembly/function.ts +38 -0
  114. package/node_modules/assemblyscript/std/assembly/index.d.ts +2822 -0
  115. package/node_modules/assemblyscript/std/assembly/iterator.ts +35 -0
  116. package/node_modules/assemblyscript/std/assembly/map.ts +260 -0
  117. package/node_modules/assemblyscript/std/assembly/math.ts +3289 -0
  118. package/node_modules/assemblyscript/std/assembly/memory.ts +123 -0
  119. package/node_modules/assemblyscript/std/assembly/number.ts +388 -0
  120. package/node_modules/assemblyscript/std/assembly/object.ts +36 -0
  121. package/node_modules/assemblyscript/std/assembly/performance.ts +9 -0
  122. package/node_modules/assemblyscript/std/assembly/polyfills.ts +27 -0
  123. package/node_modules/assemblyscript/std/assembly/process.ts +50 -0
  124. package/node_modules/assemblyscript/std/assembly/reference.ts +48 -0
  125. package/node_modules/assemblyscript/std/assembly/regexp.ts +12 -0
  126. package/node_modules/assemblyscript/std/assembly/rt/README.md +83 -0
  127. package/node_modules/assemblyscript/std/assembly/rt/common.ts +81 -0
  128. package/node_modules/assemblyscript/std/assembly/rt/index-incremental.ts +2 -0
  129. package/node_modules/assemblyscript/std/assembly/rt/index-minimal.ts +2 -0
  130. package/node_modules/assemblyscript/std/assembly/rt/index-stub.ts +1 -0
  131. package/node_modules/assemblyscript/std/assembly/rt/index.d.ts +37 -0
  132. package/node_modules/assemblyscript/std/assembly/rt/itcms.ts +419 -0
  133. package/node_modules/assemblyscript/std/assembly/rt/rtrace.ts +15 -0
  134. package/node_modules/assemblyscript/std/assembly/rt/stub.ts +133 -0
  135. package/node_modules/assemblyscript/std/assembly/rt/tcms.ts +254 -0
  136. package/node_modules/assemblyscript/std/assembly/rt/tlsf.ts +592 -0
  137. package/node_modules/assemblyscript/std/assembly/rt.ts +87 -0
  138. package/node_modules/assemblyscript/std/assembly/set.ts +220 -0
  139. package/node_modules/assemblyscript/std/assembly/shared/feature.ts +65 -0
  140. package/node_modules/assemblyscript/std/assembly/shared/runtime.ts +11 -0
  141. package/node_modules/assemblyscript/std/assembly/shared/target.ts +11 -0
  142. package/node_modules/assemblyscript/std/assembly/shared/tsconfig.json +11 -0
  143. package/node_modules/assemblyscript/std/assembly/shared/typeinfo.ts +72 -0
  144. package/node_modules/assemblyscript/std/assembly/staticarray.ts +401 -0
  145. package/node_modules/assemblyscript/std/assembly/string.ts +847 -0
  146. package/node_modules/assemblyscript/std/assembly/symbol.ts +114 -0
  147. package/node_modules/assemblyscript/std/assembly/table.ts +16 -0
  148. package/node_modules/assemblyscript/std/assembly/tsconfig.json +6 -0
  149. package/node_modules/assemblyscript/std/assembly/typedarray.ts +1945 -0
  150. package/node_modules/assemblyscript/std/assembly/uri.ts +17 -0
  151. package/node_modules/assemblyscript/std/assembly/util/bytes.ts +107 -0
  152. package/node_modules/assemblyscript/std/assembly/util/casemap.ts +497 -0
  153. package/node_modules/assemblyscript/std/assembly/util/error.ts +54 -0
  154. package/node_modules/assemblyscript/std/assembly/util/hash.ts +117 -0
  155. package/node_modules/assemblyscript/std/assembly/util/math.ts +1922 -0
  156. package/node_modules/assemblyscript/std/assembly/util/memory.ts +290 -0
  157. package/node_modules/assemblyscript/std/assembly/util/number.ts +873 -0
  158. package/node_modules/assemblyscript/std/assembly/util/sort.ts +313 -0
  159. package/node_modules/assemblyscript/std/assembly/util/string.ts +1202 -0
  160. package/node_modules/assemblyscript/std/assembly/util/uri.ts +275 -0
  161. package/node_modules/assemblyscript/std/assembly/vector.ts +4 -0
  162. package/node_modules/assemblyscript/std/assembly.json +16 -0
  163. package/node_modules/assemblyscript/std/portable/index.d.ts +462 -0
  164. package/node_modules/assemblyscript/std/portable/index.js +416 -0
  165. package/node_modules/assemblyscript/std/portable.json +11 -0
  166. package/node_modules/assemblyscript/std/types/assembly/index.d.ts +1 -0
  167. package/node_modules/assemblyscript/std/types/assembly/package.json +3 -0
  168. package/node_modules/assemblyscript/std/types/portable/index.d.ts +1 -0
  169. package/node_modules/assemblyscript/std/types/portable/package.json +3 -0
  170. package/node_modules/assemblyscript/tsconfig-base.json +13 -0
  171. package/package.json +112 -0
  172. package/runtime/deps/assemblyscript/LICENSE +201 -0
  173. package/runtime/deps/assemblyscript/NOTICE +93 -0
  174. package/runtime/deps/assemblyscript/bin/asc.js +35 -0
  175. package/runtime/deps/assemblyscript/dist/asc.js +25035 -0
  176. package/runtime/deps/assemblyscript/dist/assemblyscript.js +337 -0
  177. package/runtime/deps/assemblyscript/node_modules/binaryen/LICENSE +201 -0
  178. package/runtime/deps/assemblyscript/node_modules/binaryen/index.js +28905 -0
  179. package/runtime/deps/assemblyscript/node_modules/binaryen/package.json +50 -0
  180. package/runtime/deps/assemblyscript/node_modules/long/LICENSE +202 -0
  181. package/runtime/deps/assemblyscript/node_modules/long/index.js +1581 -0
  182. package/runtime/deps/assemblyscript/node_modules/long/package.json +58 -0
  183. package/runtime/deps/assemblyscript/package.json +114 -0
  184. package/runtime/host/amxts.inc +85 -0
  185. package/runtime/licenses/AMXX-LICENSE.txt +34 -0
  186. package/runtime/licenses/AssemblyScript-LICENSE +201 -0
  187. package/runtime/licenses/AssemblyScript-NOTICE +93 -0
  188. package/runtime/licenses/Binaryen-LICENSE +201 -0
  189. package/runtime/licenses/Bun-LICENSE.md +84 -0
  190. package/runtime/licenses/GPL-3.0.txt +674 -0
  191. package/runtime/licenses/LLVM-LICENSE.TXT +279 -0
  192. package/runtime/licenses/README.md +25 -0
  193. package/runtime/licenses/WAMR-LICENSE +219 -0
  194. package/runtime/natives.txt +1095 -0
  195. package/runtime/patches/README.md +59 -0
  196. package/runtime/patches/assemblyscript-0.28.20-amxts.patch +14174 -0
  197. package/runtime/patches/wamr-2.4.5-amxts.patch +364 -0
  198. package/scripts/apply-docs.ts +508 -0
  199. package/scripts/asc.ts +27 -0
  200. package/scripts/auto-imports.ts +258 -0
  201. package/scripts/build-wasm.ts +595 -0
  202. package/scripts/check.ts +93 -0
  203. package/scripts/compile-cache.ts +179 -0
  204. package/scripts/compile.ts +401 -0
  205. package/scripts/docker-server.ts +73 -0
  206. package/scripts/docs/as/amxts.ts +210 -0
  207. package/scripts/docs/as/effects.ts +1684 -0
  208. package/scripts/docs/as/facade.ts +3302 -0
  209. package/scripts/docs/as/fs.ts +173 -0
  210. package/scripts/docs/as/lib/check.ts +68 -0
  211. package/scripts/docs/as/modules/http.ts +66 -0
  212. package/scripts/docs/as/os.ts +16 -0
  213. package/scripts/docs/as/promise.ts +132 -0
  214. package/scripts/docs/as/promise.types.ts +63 -0
  215. package/scripts/docs/as/vector.ts +48 -0
  216. package/scripts/docs/entities.ts +2338 -0
  217. package/scripts/docs/events.ts +245 -0
  218. package/scripts/docs/game.ts +497 -0
  219. package/scripts/docs/messages.ts +148 -0
  220. package/scripts/file-contents.ts +74 -0
  221. package/scripts/includes.ts +134 -0
  222. package/scripts/player-fields.ts +866 -0
  223. package/scripts/plugin-cache.ts +92 -0
  224. package/scripts/plugin-natives.ts +1157 -0
  225. package/scripts/prebuilt.ts +268 -0
  226. package/scripts/prepare.ts +134 -0
  227. package/scripts/project.ts +973 -0
  228. package/scripts/run.ts +28 -0
  229. package/scripts/shared-modules.ts +906 -0
  230. package/scripts/system.ts +33 -0
  231. package/scripts/tracked-fs.ts +115 -0
  232. package/scripts/typed-configs.ts +788 -0
  233. package/scripts/ui.ts +77 -0
  234. package/src/cli-api.mjs +175 -0
  235. package/src/parser/include-parser.ts +506 -0
  236. package/src/system.mjs +155 -0
  237. package/src/testing/compile-cache.ts +31 -0
  238. package/src/testing/compile.ts +222 -0
  239. package/src/testing/coroutines.ts +191 -0
  240. package/src/testing/index.ts +39 -0
  241. package/src/testing/kits.ts +55 -0
  242. package/src/testing/memory.ts +125 -0
  243. package/src/testing/natives.ts +925 -0
  244. package/src/testing/server.ts +1874 -0
  245. package/src/testing/setup.ts +63 -0
  246. package/src/testing/tables.ts +139 -0
  247. package/src/types.ts +51 -0
@@ -0,0 +1,925 @@
1
+ // The natives the fake server answers, as AMX Mod X and its modules would.
2
+ //
3
+ // Each takes the Pawn frame as the plugin pushed it: a number is a cell, a
4
+ // float its bit pattern, and a string or a buffer the address of cells in the
5
+ // plugin's memory. A `...` tail comes through the dispatcher with every
6
+ // argument by address, as Pawn pushes one - so a native reads its tail through
7
+ // `memory.cell(ptr)` exactly as AMX Mod X reads it through the AMX.
8
+ //
9
+ // This is the part that grows. A native a plugin calls and nothing here
10
+ // answers stops the test with its name; add it below, keeping to what the real
11
+ // one does closely enough for a test to be believed.
12
+ import type { Memory } from './memory';
13
+ import type { FakePlayer, FakeServer, PluginInstance, SentForward, TeamName, Value } from './server';
14
+ import { bitsFloat, floatBits } from './memory';
15
+ import { FakeEntity, flagBits, normalizePath, TEAMS } from './server';
16
+ import { constant, tables } from './tables';
17
+
18
+ export interface NativeCall {
19
+ server: FakeServer;
20
+ plugin: PluginInstance;
21
+ memory: Memory;
22
+ }
23
+
24
+ export type Native = (call: NativeCall, args: number[]) => number | void;
25
+
26
+ /** Every weapon by its WeaponIdType - the facade's WEAPON_IDS. */
27
+ export const WEAPON_NAMES = [
28
+ '',
29
+ 'weapon_p228',
30
+ '',
31
+ 'weapon_scout',
32
+ 'weapon_hegrenade',
33
+ 'weapon_xm1014',
34
+ 'weapon_c4',
35
+ 'weapon_mac10',
36
+ 'weapon_aug',
37
+ 'weapon_smokegrenade',
38
+ 'weapon_elite',
39
+ 'weapon_fiveseven',
40
+ 'weapon_ump45',
41
+ 'weapon_sg550',
42
+ 'weapon_galil',
43
+ 'weapon_famas',
44
+ 'weapon_usp',
45
+ 'weapon_glock18',
46
+ 'weapon_awp',
47
+ 'weapon_mp5navy',
48
+ 'weapon_m249',
49
+ 'weapon_m3',
50
+ 'weapon_m4a1',
51
+ 'weapon_tmp',
52
+ 'weapon_g3sg1',
53
+ 'weapon_flashbang',
54
+ 'weapon_deagle',
55
+ 'weapon_sg552',
56
+ 'weapon_ak47',
57
+ 'weapon_knife',
58
+ 'weapon_p90',
59
+ ];
60
+
61
+ /**
62
+ * Pawn's format: the conversions AMX Mod X plugins use, each reading its
63
+ * argument through the address the tail holds.
64
+ */
65
+ export function formatPawn(call: NativeCall, format: string, tail: number[]): string {
66
+ let next = 0;
67
+
68
+ return format.replace(/%(\.(\d+))?([dicsfxXn%])/g, (all, _, precision, kind) => {
69
+ if (kind === '%') return '%';
70
+ const at = tail[next++];
71
+ if (at === undefined) return '';
72
+
73
+ const { memory } = call;
74
+ switch (kind) {
75
+ case 'd': case 'i': return String(memory.cell(at));
76
+ case 'c': return String.fromCharCode(memory.cell(at));
77
+ case 's': return memory.text(at);
78
+ case 'f': return memory.float(at).toFixed(precision ? Number(precision) : 6);
79
+ case 'x': return (memory.cell(at) >>> 0).toString(16);
80
+ case 'X': return (memory.cell(at) >>> 0).toString(16).toUpperCase();
81
+ case 'n': return call.server.player(memory.cell(at))?.name ?? '';
82
+ }
83
+ return all;
84
+ });
85
+ }
86
+
87
+ function player(call: NativeCall, id: number): FakePlayer | undefined {
88
+ return call.server.player(id);
89
+ }
90
+
91
+ function entity(call: NativeCall, id: number): FakeEntity | undefined {
92
+ return call.server.entities.get(id);
93
+ }
94
+
95
+ /** A float field's value as the cell a native returns: its bits; anything else as it is. */
96
+ function cellOf(field: number, value: number | number[] | string): number {
97
+ if (typeof value !== 'number') return 0;
98
+ return tables().floatFields.has(field) ? floatBits(value) : value | 0;
99
+ }
100
+
101
+ /** get_entvar and get_member: a vector into the tail's buffer, text into its buffer, a cell returned. */
102
+ function readField(call: NativeCall, target: FakeEntity | undefined, field: number, tail: number[], element?: number): number {
103
+ if (!target) return 0;
104
+ const { vectorFields, stringFields } = tables();
105
+ const value = target.fields.get(FakeEntity.key(field, element)) ?? (vectorFields.has(field) ? [0, 0, 0] : stringFields.has(field) ? '' : 0);
106
+
107
+ if (Array.isArray(value)) {
108
+ if (tail[0]) call.memory.setVector(tail[0], value);
109
+ return 1;
110
+ }
111
+ if (typeof value === 'string') {
112
+ if (tail[0]) return call.memory.setText(tail[0], tail[1] ? call.memory.cell(tail[1]) : 255, value);
113
+ return 0;
114
+ }
115
+ return cellOf(field, value);
116
+ }
117
+
118
+ /** set_entvar and set_member: the value is at the tail's first address, typed by the field. */
119
+ function writeField(call: NativeCall, target: FakeEntity | undefined, field: number, at: number, element?: number): number {
120
+ if (!target || !at) return 0;
121
+ const { floatFields, vectorFields, stringFields } = tables();
122
+
123
+ const value = vectorFields.has(field)
124
+ ? call.memory.vector(at)
125
+ : stringFields.has(field)
126
+ ? call.memory.text(at)
127
+ : floatFields.has(field)
128
+ ? call.memory.float(at)
129
+ : call.memory.cell(at);
130
+
131
+ target.fields.set(FakeEntity.key(field, element), value);
132
+ return 1;
133
+ }
134
+
135
+ const entvars = new Map<string, number>();
136
+
137
+ /** An entity_get_* key (EV_SZ_classname) as the entvar it names (var_classname). */
138
+ function entvarOf(prefix: string, key: number): number {
139
+ const known = entvars.get(`${prefix}${key}`);
140
+ if (known !== undefined) return known;
141
+ const name = [...tables().constants].find(([n, v]) => v === key && n.startsWith(prefix))?.[0];
142
+ const field = name ? tables().constants.get(`var_${name.slice(prefix.length)}`) : undefined;
143
+ if (field === undefined) throw new Error(`no entvar behind ${prefix}${key}`);
144
+ entvars.set(`${prefix}${key}`, field);
145
+ return field;
146
+ }
147
+
148
+ function kill(target: FakePlayer, keepFrags: boolean): void {
149
+ target.alive = false;
150
+ target.health = 0;
151
+ target.deaths += 1;
152
+ if (!keepFrags) target.frags -= 1;
153
+ }
154
+
155
+ /** get_players' own filtering, flag letter by flag letter. */
156
+ function playersMatching(call: NativeCall, flags: string, team: string): FakePlayer[] {
157
+ return call.server.players.filter(p =>
158
+ !(flags.includes('a') && !p.alive)
159
+ && !(flags.includes('b') && p.alive)
160
+ && !(flags.includes('c') && p.bot)
161
+ && !(flags.includes('d') && !p.bot)
162
+ && !(flags.includes('e') && p.team !== team)
163
+ && !(flags.includes('f') && !p.name.includes(team)),
164
+ );
165
+ }
166
+
167
+ function cvarNamed(call: NativeCall, at: number) {
168
+ return call.server.cvars.get(call.memory.text(at).toLowerCase());
169
+ }
170
+
171
+ /** How the engine writes a number into a cvar: whole ones without decimals. */
172
+ function cvarNumber(value: number): string {
173
+ return Number.isInteger(value) ? String(value) : value.toFixed(6);
174
+ }
175
+
176
+ function giveWeapon(target: FakePlayer | undefined, name: string): number {
177
+ if (!target) return 0;
178
+ if (name === 'item_kevlar' || name === 'item_assaultsuit') {
179
+ target.armor = 100;
180
+ return 0;
181
+ }
182
+ if (!WEAPON_NAMES.includes(name) || !name) return 0;
183
+ return target.give(name).id;
184
+ }
185
+
186
+ function weaponByName(target: FakePlayer | undefined, name: string) {
187
+ return target?.items.find(w => w.kind === name);
188
+ }
189
+
190
+ function switchTo(target: FakePlayer | undefined, name: string): number {
191
+ const weapon = weaponByName(target, name);
192
+ if (!target || !weapon) return 0;
193
+ target.set('m_pActiveItem', weapon.id);
194
+ return 1;
195
+ }
196
+
197
+ function chain(call: NativeCall) {
198
+ const running = call.server.chain;
199
+ if (!running) throw new Error('a hookchain native was called outside a hookchain');
200
+ return running;
201
+ }
202
+
203
+ /** A hookchain value read through the tail's address, by its ATYPE_*. */
204
+ function chainValue(call: NativeCall, type: number, at: number): Value {
205
+ if (type === constant('ATYPE_FLOAT')) return call.memory.float(at);
206
+ if (type === constant('ATYPE_BOOL')) return call.memory.cell(at) !== 0;
207
+ if (type === constant('ATYPE_STRING')) return call.memory.text(at);
208
+ return call.memory.cell(at);
209
+ }
210
+
211
+ /** The message register_message's callbacks are on: server.sendMessage's. */
212
+ function hooked(call: NativeCall) {
213
+ const message = call.server.hookedMessage;
214
+ if (!message) throw new Error('a message native was called outside a register_message callback');
215
+ return message;
216
+ }
217
+
218
+ function setMessageArg(call: NativeCall, arg: number, value: number | string): number {
219
+ hooked(call).args[arg - 1] = value;
220
+ return 1;
221
+ }
222
+
223
+ /** ExecuteHam(B): the call kept, and with hooks the class's listeners run - the function's answer. */
224
+ function executeHam(call: NativeCall, fn: number, id: number, tail: number[], hooks: boolean): number {
225
+ const shape = [...tables().hooks.values()].find(one => one.ham === fn);
226
+ const args = tail.map((at, i) => shape?.floats.has(i + 1) ? call.memory.float(at) : call.memory.cell(at));
227
+ call.server.hamCalls.push({ fn, entity: id, args, hooks });
228
+ if (!hooks || !shape) return 0;
229
+ return Number(call.server.runHam(shape, id, args).result) | 0;
230
+ }
231
+
232
+ /** SetHamParam*: `which` counts from 1, the entity itself. */
233
+ function setHamParam(call: NativeCall, which: number, value: Value): number {
234
+ chain(call).args[which - 1] = value;
235
+ return 1;
236
+ }
237
+
238
+ function setHamReturn(call: NativeCall, value: Value): number {
239
+ chain(call).answer = value;
240
+ chain(call).answered = true;
241
+ return 1;
242
+ }
243
+
244
+ // The engine's own message for a temporary effect: it is no user message, so
245
+ // no name get_user_msgid handed out stands for it.
246
+ const SVC_TEMPENTITY = 23;
247
+
248
+ /**
249
+ * message_begin: a user message by its name, or a temporary effect - with
250
+ * who it goes to (`dest`, MSG_*) and the point it is seen from.
251
+ */
252
+ function beginMessage(call: NativeCall, dest: number, type: number, player: number, origin: number[]): number {
253
+ if (type === SVC_TEMPENTITY) {
254
+ call.server.writing = { name: 'SVC_TEMPENTITY', player, args: [], dest, origin };
255
+ return 1;
256
+ }
257
+ const name = [...call.server.messageIds].find(([, id]) => id === type)?.[0];
258
+ call.server.writing = name ? { name, player, args: [] } : null;
259
+ return 1;
260
+ }
261
+
262
+ function sendText(call: NativeCall, id: number, variant: 'chat' | 'center' | 'console' | 'notify' | 'hud', text: string): void {
263
+ for (const target of id > 0 ? [player(call, id)] : call.server.players) target?.show(variant, text);
264
+ }
265
+
266
+ const PRINT_VARIANTS = ['', 'notify', 'console', 'chat', 'center'] as const;
267
+
268
+ /** A Pawn string of UTF-8 bytes - a path, a line of a file - as text. */
269
+ function bytesText(call: NativeCall, pointer: number): string {
270
+ return call.memory.text(pointer, 65536);
271
+ }
272
+
273
+ function bytesAt(call: NativeCall, pointer: number): Uint8Array {
274
+ return call.memory.bytes(pointer, 65536);
275
+ }
276
+
277
+ let nextHandle = 1;
278
+ let hudSyncObjects = 0;
279
+
280
+ /** fopen: `r` needs the file, `w` empties it, `a` writes at its end; the folder must be there. */
281
+ function openFile(call: NativeCall, path: string, mode: string): number {
282
+ const { server } = call;
283
+ const file = normalizePath(path);
284
+ const folder = file.includes('/') ? file.slice(0, file.lastIndexOf('/')) : '';
285
+
286
+ if (mode.startsWith('r') && !server.files.has(file)) return 0;
287
+ if (!mode.startsWith('r') && !server.folderExists(folder)) return 0;
288
+ if (mode.startsWith('w') || !server.files.has(file)) server.files.set(file, new Uint8Array(0));
289
+
290
+ const handle = nextHandle++;
291
+ server.handles.set(handle, { path: file, position: mode.startsWith('a') ? server.files.get(file)!.length : 0 });
292
+ return handle;
293
+ }
294
+
295
+ function openedFile(call: NativeCall, handle: number) {
296
+ const open = call.server.handles.get(handle);
297
+ return open && 'path' in open ? open : undefined;
298
+ }
299
+
300
+ /** What open_dir lists: `.`, `..`, then every file and folder right inside. */
301
+ function folderEntries(call: NativeCall, path: string): string[] {
302
+ const folder = normalizePath(path);
303
+ const prefix = folder === '' ? '' : `${folder}/`;
304
+ const names = new Set<string>();
305
+ for (const known of [...call.server.files.keys(), ...call.server.folders]) {
306
+ if (known.startsWith(prefix) && known.length > prefix.length) names.add(known.slice(prefix.length).split('/')[0]);
307
+ }
308
+ return ['.', '..', ...[...names].sort()];
309
+ }
310
+
311
+ export const NATIVES: Record<string, Native> = {
312
+ // ------------------------------------------------------------ files
313
+
314
+ fopen: (c, [name, mode]) => openFile(c, bytesText(c, name), c.memory.text(mode)),
315
+ fclose: (c, [handle]) => +c.server.handles.delete(handle),
316
+ // BLOCK_CHAR: a byte a cell.
317
+ fread_blocks: (c, [handle, data, blocks]) => {
318
+ const open = openedFile(c, handle);
319
+ const bytes = open && c.server.files.get(open.path);
320
+ if (!open || !bytes) return 0;
321
+ const count = Math.max(0, Math.min(blocks, bytes.length - open.position));
322
+ for (let i = 0; i < count; i++) c.memory.setCell(data + i * 4, bytes[open.position + i]);
323
+ open.position += count;
324
+ return count;
325
+ },
326
+ fputs: (c, [handle, text]) => {
327
+ const open = openedFile(c, handle);
328
+ if (!open) return 0;
329
+ const before = c.server.files.get(open.path) ?? new Uint8Array(0);
330
+ const added = bytesAt(c, text);
331
+ const after = new Uint8Array(Math.max(before.length, open.position + added.length));
332
+ after.set(before);
333
+ after.set(added, open.position);
334
+ c.server.files.set(open.path, after);
335
+ open.position += added.length;
336
+ return added.length;
337
+ },
338
+ file_exists: (c, [name]) => +c.server.files.has(normalizePath(bytesText(c, name))),
339
+ dir_exists: (c, [name]) => +c.server.folderExists(bytesText(c, name)),
340
+ open_dir: (c, [dir, first, length]) => {
341
+ if (!c.server.folderExists(bytesText(c, dir))) return 0;
342
+ const entries = folderEntries(c, bytesText(c, dir));
343
+ c.memory.setText(first, length, entries[0]);
344
+ const handle = nextHandle++;
345
+ c.server.handles.set(handle, { entries, next: 1 });
346
+ return handle;
347
+ },
348
+ next_file: (c, [handle, buffer, length]) => {
349
+ const open = c.server.handles.get(handle);
350
+ if (!open || !('entries' in open) || open.next >= open.entries.length) return 0;
351
+ c.memory.setText(buffer, length, open.entries[open.next++]);
352
+ return 1;
353
+ },
354
+ close_dir: (c, [handle]) => +c.server.handles.delete(handle),
355
+ // 0 when made; the parent has to be there, as for the real one.
356
+ mkdir: (c, [name]) => {
357
+ const folder = normalizePath(bytesText(c, name));
358
+ const parent = folder.includes('/') ? folder.slice(0, folder.lastIndexOf('/')) : '';
359
+ if (c.server.folderExists(folder) || !c.server.folderExists(parent)) return -1;
360
+ c.server.folders.add(folder);
361
+ return 0;
362
+ },
363
+ get_localinfo: (c, [info, buffer, length]) =>
364
+ c.memory.setText(buffer, length, c.server.localinfo.get(c.memory.text(info)) ?? ''),
365
+
366
+ // ------------------------------------------------------------ dynamic arrays (cellarray.inc)
367
+ // An item is its cells; text is a byte a cell, as ArrayPushString stores it.
368
+
369
+ ArrayCreate: (c, [cellSize]) => c.server.createCellArray(Math.max(1, cellSize)),
370
+ ArrayPushString: (c, [handle, text]) => {
371
+ const array = c.server.cellArrays.get(handle);
372
+ if (!array) return -1;
373
+ const bytes = [...bytesAt(c, text)].slice(0, array.cellSize - 1);
374
+ array.items.push([...bytes, 0]);
375
+ return array.items.length - 1;
376
+ },
377
+ ArrayPushCell: (c, [handle, value]) => {
378
+ const array = c.server.cellArrays.get(handle);
379
+ if (!array) return -1;
380
+ array.items.push([value]);
381
+ return array.items.length - 1;
382
+ },
383
+ ArrayPushArray: (c, [handle, cells, size]) => {
384
+ const array = c.server.cellArrays.get(handle);
385
+ if (!array) return -1;
386
+ const count = size < 0 ? array.cellSize : Math.min(size, array.cellSize);
387
+ const item: number[] = [];
388
+ for (let i = 0; i < count; i++) item.push(c.memory.cell(cells + i * 4));
389
+ array.items.push(item);
390
+ return array.items.length - 1;
391
+ },
392
+ ArraySize: (c, [handle]) => c.server.cellArrays.get(handle)?.items.length ?? 0,
393
+ ArrayGetCell: (c, [handle, index]) => c.server.cellArrays.get(handle)?.items[index]?.[0] ?? 0,
394
+ ArrayGetString: (c, [handle, index, buffer, length]) => {
395
+ const item = c.server.cellArrays.get(handle)?.items[index] ?? [0];
396
+ const bytes = item.slice(0, Math.max(0, !item.includes(0) ? item.length : item.indexOf(0)));
397
+ const count = Math.min(bytes.length, length);
398
+ for (let i = 0; i < count; i++) c.memory.setCell(buffer + i * 4, bytes[i]);
399
+ c.memory.setCell(buffer + count * 4, 0);
400
+ return count;
401
+ },
402
+ ArrayGetArray: (c, [handle, index, buffer, size]) => {
403
+ const item = c.server.cellArrays.get(handle)?.items[index] ?? [];
404
+ const count = size < 0 ? item.length : Math.min(size, item.length);
405
+ for (let i = 0; i < count; i++) c.memory.setCell(buffer + i * 4, item[i]);
406
+ return count;
407
+ },
408
+ // ArrayDestroy takes the handle by reference and zeroes it.
409
+ ArrayDestroy: (c, [at]) => {
410
+ const handle = c.memory.cell(at);
411
+ c.memory.setCell(at, 0);
412
+ return +c.server.cellArrays.delete(handle);
413
+ },
414
+
415
+ // ------------------------------------------------------------ the server
416
+
417
+ get_maxplayers: c => c.server.maxPlayers,
418
+ get_playersnum: c => c.server.players.length,
419
+ get_mapname: (c, [buffer, length]) => c.memory.setText(buffer, length, c.server.map),
420
+ get_gametime: c => floatBits(c.server.time / 1000),
421
+ LibraryExists: (c, [name]) => +c.server.modules.has(c.memory.text(name)),
422
+
423
+ server_cmd: (c, [format, ...tail]) => {
424
+ c.server.commands.push(formatPawn(c, c.memory.text(format), tail));
425
+ },
426
+ server_print: (c, [format, ...tail]) => {
427
+ c.server.logLines.push(formatPawn(c, c.memory.text(format), tail));
428
+ },
429
+ client_cmd: (c, [id, format, ...tail]) => {
430
+ const line = formatPawn(c, c.memory.text(format), tail);
431
+ for (const target of id > 0 ? [player(c, id)] : c.server.players) target?.commands.push(line);
432
+ },
433
+ client_print: (c, [id, type, format, ...tail]) => {
434
+ sendText(c, id, PRINT_VARIANTS[type] || 'chat', formatPawn(c, c.memory.text(format), tail));
435
+ },
436
+ console_print: (c, [id, format, ...tail]) => {
437
+ const line = formatPawn(c, c.memory.text(format), tail);
438
+ if (id > 0) sendText(c, id, 'console', line);
439
+ else c.server.logLines.push(line);
440
+ },
441
+
442
+ set_hudmessage: (c, [r, g, b, x, y, _effects, _fxtime, hold, _fadein, _fadeout, channel]) => {
443
+ c.server.hud = { color: [r, g, b], x: bitsFloat(x), y: bitsFloat(y), hold: bitsFloat(hold), channel };
444
+ },
445
+ show_hudmessage: (c, [id, format, ...tail]) => sendText(c, id, 'hud', formatPawn(c, c.memory.text(format), tail)),
446
+
447
+ // ------------------------------------------------------------ the command being run
448
+
449
+ read_argc: c => c.server.argv.length,
450
+ read_argv: (c, [index, buffer, length]) => c.memory.setText(buffer, length, c.server.argv[index] ?? ''),
451
+ read_args: (c, [buffer, length]) => c.memory.setText(buffer, length, c.server.argv.slice(1).join(' ')),
452
+
453
+ // ------------------------------------------------------------ players
454
+
455
+ is_user_connected: (c, [id]) => +!!player(c, id),
456
+ is_user_alive: (c, [id]) => +!!player(c, id)?.alive,
457
+ is_user_bot: (c, [id]) => +!!player(c, id)?.bot,
458
+ is_user_hltv: () => 0,
459
+ get_user_name: (c, [id, buffer, length]) => c.memory.setText(buffer, length, id === 0 ? 'Console' : player(c, id)?.name ?? ''),
460
+ get_user_authid: (c, [id, buffer, length]) => c.memory.setText(buffer, length, player(c, id)?.authid ?? ''),
461
+ get_user_ip: (c, [id, buffer, length, withoutPort]) => {
462
+ const ip = player(c, id)?.ip ?? '';
463
+ return c.memory.setText(buffer, length, withoutPort ? ip.replace(/:\d+$/, '') : ip);
464
+ },
465
+ get_user_flags: (c, [id]) => player(c, id)?.flags ?? 0,
466
+ read_flags: (c, [text]) => flagBits(c.memory.text(text)),
467
+ get_user_health: (c, [id]) => Math.trunc(player(c, id)?.health ?? 0),
468
+ // fun's: nothing left is the game's ClientKill, which a dead player is past -
469
+ // user_kill without its frags. set_entvar's var_health kills nobody.
470
+ set_user_health: (c, [id, health]) => {
471
+ if (health <= 0) return NATIVES.user_kill(c, [id, 0]);
472
+ const target = player(c, id);
473
+ if (target) target.health = health;
474
+ },
475
+ get_user_armor: (c, [id]) => Math.trunc(player(c, id)?.armor ?? 0),
476
+ set_user_armor: (c, [id, armor]) => {
477
+ const t = player(c, id);
478
+ if (t) t.armor = armor;
479
+ },
480
+ get_user_frags: (c, [id]) => Math.trunc(player(c, id)?.frags ?? 0),
481
+ set_user_frags: (c, [id, frags]) => {
482
+ const t = player(c, id);
483
+ if (t) t.frags = frags;
484
+ },
485
+ get_user_deaths: (c, [id]) => player(c, id)?.deaths ?? 0,
486
+ cs_set_user_deaths: (c, [id, deaths]) => {
487
+ const t = player(c, id);
488
+ if (t) t.deaths = deaths;
489
+ },
490
+ get_user_team: (c, [id, buffer, length]) => {
491
+ const target = player(c, id);
492
+ if (!target) return 0;
493
+ if (buffer) c.memory.setText(buffer, length, target.team === 'UNASSIGNED' ? '' : target.team);
494
+ return TEAMS.indexOf(target.team);
495
+ },
496
+ cs_get_user_team: (c, [id]) => TEAMS.indexOf(player(c, id)?.team ?? 'UNASSIGNED'),
497
+ cs_set_user_team: (c, [id, team]) => {
498
+ const t = player(c, id);
499
+ if (t) t.team = TEAMS[team] as TeamName;
500
+ },
501
+ rg_set_user_team: (c, [id, team]) => {
502
+ const target = player(c, id);
503
+ if (!target) return 0;
504
+ target.team = TEAMS[team] as TeamName;
505
+ return 1;
506
+ },
507
+ get_players: (c, [list, count, flags, team]) => {
508
+ const found = playersMatching(c, c.memory.text(flags), c.memory.text(team));
509
+ found.forEach((p, i) => c.memory.setCell(list + i * 4, p.id));
510
+ c.memory.setCell(count, found.length);
511
+ },
512
+ user_kill: (c, [id, keepFrags]) => {
513
+ const target = player(c, id);
514
+ if (target?.alive) kill(target, keepFrags !== 0);
515
+ },
516
+ rg_round_respawn: (c, [id]) => {
517
+ const target = player(c, id);
518
+ if (!target) return;
519
+ target.alive = true;
520
+ target.health = 100;
521
+ },
522
+ get_speak: (c, [id]) => player(c, id)?.speak ?? 0,
523
+ set_speak: (c, [id, flags]) => {
524
+ const t = player(c, id);
525
+ if (t) t.speak = flags;
526
+ },
527
+
528
+ // ------------------------------------------------------------ weapons
529
+
530
+ give_item: (c, [id, name]) => giveWeapon(player(c, id), c.memory.text(name)),
531
+ strip_user_weapons: (c, [id]) => {
532
+ player(c, id)?.stripWeapons();
533
+ },
534
+ // reapi's reach only a weapon the player carries; cstrike's any.
535
+ rg_set_user_bpammo: (c, [id, weapon, amount]) => {
536
+ const target = player(c, id);
537
+ if (weaponByName(target, WEAPON_NAMES[weapon])) target?.ammo.set(WEAPON_NAMES[weapon], amount);
538
+ },
539
+ rg_get_user_bpammo: (c, [id, weapon]) => {
540
+ const target = player(c, id);
541
+ return weaponByName(target, WEAPON_NAMES[weapon]) ? target?.ammo.get(WEAPON_NAMES[weapon]) ?? 0 : 0;
542
+ },
543
+ cs_set_user_bpammo: (c, [id, weapon, amount]) => {
544
+ player(c, id)?.ammo.set(WEAPON_NAMES[weapon], amount);
545
+ },
546
+ cs_get_user_bpammo: (c, [id, weapon]) => player(c, id)?.ammo.get(WEAPON_NAMES[weapon]) ?? 0,
547
+ rg_find_weapon_bpack_by_name: (c, [id, name]) => weaponByName(player(c, id), c.memory.text(name))?.id ?? 0,
548
+ rg_switch_weapon: (c, [id, weapon]) => switchTo(player(c, id), entity(c, weapon)?.classname ?? ''),
549
+ user_has_weapon: (c, [id, weapon]) => +!!weaponByName(player(c, id), WEAPON_NAMES[weapon] ?? ''),
550
+ engclient_cmd: (c, [id, command, arg1, arg2]) => {
551
+ const target = player(c, id);
552
+ const line = [command, arg1, arg2].map(p => c.memory.text(p)).filter(Boolean).join(' ');
553
+ target?.commands.push(line);
554
+ if (line.startsWith('weapon_')) switchTo(target, line);
555
+ // The team menu's slots, as the game handles `jointeam`.
556
+ const side = { 'jointeam 1': 'TERRORIST', 'jointeam 2': 'CT', 'jointeam 6': 'SPECTATOR' }[line];
557
+ if (target && side) target.team = side as TeamName;
558
+ },
559
+ rg_reset_maxspeed: () => {},
560
+
561
+ // ------------------------------------------------------------ entities
562
+
563
+ get_entvar: (c, [id, field, ...tail]) => readField(c, entity(c, id), field, tail),
564
+ set_entvar: (c, [id, field, at]) => writeField(c, entity(c, id), field, at),
565
+ get_member: (c, [id, field, ...tail]) => {
566
+ const element = tables().arrayFields.has(field) ? (tail[0] ? c.memory.cell(tail[0]) : 0) : undefined;
567
+ return readField(c, entity(c, id), field, element === undefined ? tail : tail.slice(1), element);
568
+ },
569
+ set_member: (c, [id, field, at, elementAt]) => {
570
+ const element = tables().arrayFields.has(field) ? (elementAt ? c.memory.cell(elementAt) : 0) : undefined;
571
+ return writeField(c, entity(c, id), field, at, element);
572
+ },
573
+ entity_get_string: (c, [id, key, buffer, length]) =>
574
+ c.memory.setText(buffer, length, String(entity(c, id)?.fields.get(FakeEntity.key(entvarOf('EV_SZ_', key))) ?? '')),
575
+ entity_set_string: (c, [id, key, text]) => {
576
+ entity(c, id)?.fields.set(FakeEntity.key(entvarOf('EV_SZ_', key)), c.memory.text(text));
577
+ },
578
+ entity_get_int: (c, [id, key]) => Number(entity(c, id)?.fields.get(FakeEntity.key(entvarOf('EV_INT_', key))) ?? 0) | 0,
579
+ entity_set_int: (c, [id, key, value]) => {
580
+ entity(c, id)?.fields.set(FakeEntity.key(entvarOf('EV_INT_', key)), value);
581
+ },
582
+ entity_get_float: (c, [id, key]) => floatBits(Number(entity(c, id)?.fields.get(FakeEntity.key(entvarOf('EV_FL_', key))) ?? 0)),
583
+ entity_set_float: (c, [id, key, value]) => {
584
+ entity(c, id)?.fields.set(FakeEntity.key(entvarOf('EV_FL_', key)), bitsFloat(value));
585
+ },
586
+ entity_get_edict: (c, [id, key]) => Number(entity(c, id)?.fields.get(FakeEntity.key(entvarOf('EV_ENT_', key))) ?? 0) | 0,
587
+ entity_set_edict: (c, [id, key, value]) => {
588
+ entity(c, id)?.fields.set(FakeEntity.key(entvarOf('EV_ENT_', key)), value);
589
+ },
590
+ entity_get_vector: (c, [id, key, at]) => {
591
+ const value = entity(c, id)?.fields.get(FakeEntity.key(entvarOf('EV_VEC_', key)));
592
+ c.memory.setVector(at, Array.isArray(value) ? value : [0, 0, 0]);
593
+ },
594
+ entity_set_vector: (c, [id, key, at]) => {
595
+ entity(c, id)?.fields.set(FakeEntity.key(entvarOf('EV_VEC_', key)), c.memory.vector(at));
596
+ },
597
+ entity_set_origin: (c, [id, at]) => {
598
+ const target = entity(c, id);
599
+ if (target) target.origin = c.memory.vector(at);
600
+ },
601
+ create_entity: (c, [classname]) => c.server.createEntity(c.memory.text(classname)).id,
602
+ remove_entity: (c, [id]) => +c.server.entities.delete(id),
603
+ is_valid_ent: (c, [id]) => +c.server.entities.has(id),
604
+ find_ent_by_class: (c, [start, classname]) => {
605
+ const name = c.memory.text(classname);
606
+ const found = [...c.server.entities.values()].find(e => e.id > start && e.classname === name);
607
+ return found?.id ?? 0;
608
+ },
609
+ find_ent_in_sphere: (c, [start, at, radius]) => {
610
+ const center = c.memory.vector(at);
611
+ const reach = bitsFloat(radius);
612
+ const found = [...c.server.entities.values()].find(e => e.id > start && Math.hypot(...e.origin.map((v, k) => v - center[k])) <= reach);
613
+ return found?.id ?? 0;
614
+ },
615
+ get_global_int: (c, [key]) => key === constant('GL_maxEntities') ? Math.max(0, ...c.server.entities.keys()) + 1 : 0,
616
+
617
+ // ------------------------------------------------------------ hookchains (reapi)
618
+
619
+ GetHookChainReturn: (c, [type, buffer, lengthAt]) => {
620
+ const answer = chain(c).answer;
621
+ if (type === constant('ATYPE_STRING')) return c.memory.setText(buffer, lengthAt ? c.memory.cell(lengthAt) : 255, String(answer));
622
+ if (type === constant('ATYPE_FLOAT')) return floatBits(Number(answer));
623
+ return typeof answer === 'boolean' ? +answer : Number(answer) | 0;
624
+ },
625
+ SetHookChainReturn: (c, [type, at]) => {
626
+ chain(c).answer = chainValue(c, type, at);
627
+ chain(c).answered = true;
628
+ return 1;
629
+ },
630
+ SetHookChainArg: (c, [number, type, at]) => {
631
+ chain(c).args[number - 1] = chainValue(c, type, at);
632
+ return 1;
633
+ },
634
+
635
+ // ------------------------------------------------------------ forwards
636
+
637
+ CreateMultiForward: (c, [name, _stop, ...types]) => {
638
+ c.server.forwardHandles.push({ name: c.memory.text(name), types: types.map(at => c.memory.cell(at)) });
639
+ return c.server.forwardHandles.length - 1;
640
+ },
641
+ ExecuteForward: (c, [handle, returnAt, ...tail]) => {
642
+ const forward = c.server.forwardHandles[handle];
643
+ if (!forward) return 0;
644
+ // An array arrives as the dispatcher's `a` argument: its count, then its cells.
645
+ const read: Record<number, (at: number) => SentForward['args'][number]> = {
646
+ [constant('FP_STRING')]: at => c.memory.text(at),
647
+ [constant('FP_FLOAT')]: at => c.memory.float(at),
648
+ [constant('FP_ARRAY')]: at => Array.from({ length: c.memory.cell(at) }, (_, k) => c.memory.cell(at + 4 + k * 4)),
649
+ };
650
+ const args = tail.map((at, i) => (read[forward.types[i]] ?? (a => c.memory.cell(a)))(at));
651
+ c.server.forwards.push({ name: forward.name, args });
652
+ if (returnAt) c.memory.setCell(returnAt, 0);
653
+ return 1;
654
+ },
655
+ DestroyForward: () => 1,
656
+
657
+ // ------------------------------------------------------------ nVault
658
+
659
+ nvault_open: (c, [name]) => {
660
+ const vault = c.memory.text(name);
661
+ c.server.vault(vault);
662
+ c.server.vaultHandles.push(vault);
663
+ return c.server.vaultHandles.length - 1;
664
+ },
665
+ nvault_close: () => {},
666
+ nvault_set: (c, [handle, key, value]) => {
667
+ c.server.vault(c.server.vaultHandles[handle]).set(c.memory.text(key), c.memory.text(value));
668
+ },
669
+ nvault_lookup: (c, [handle, key, value, length, stampAt]) => {
670
+ const found = c.server.vault(c.server.vaultHandles[handle]).get(c.memory.text(key));
671
+ if (found === undefined) return 0;
672
+ c.memory.setText(value, length, found);
673
+ if (stampAt) c.memory.setCell(stampAt, 0);
674
+ return 1;
675
+ },
676
+ nvault_remove: (c, [handle, key]) => {
677
+ c.server.vault(c.server.vaultHandles[handle]).delete(c.memory.text(key));
678
+ },
679
+
680
+ // ------------------------------------------------------------ cvars
681
+
682
+ create_cvar: (c, [name, value]) => {
683
+ const existing = cvarNamed(c, name);
684
+ return (existing ?? c.server.createCvar(c.memory.text(name), c.memory.text(value))).pointer;
685
+ },
686
+ get_cvar_pointer: (c, [name]) => cvarNamed(c, name)?.pointer ?? 0,
687
+ cvar_exists: (c, [name]) => +!!cvarNamed(c, name),
688
+ get_pcvar_string: (c, [pointer, buffer, length]) => c.memory.setText(buffer, length, c.server.cvarByPointer(pointer)?.value ?? ''),
689
+ get_pcvar_num: (c, [pointer]) => Math.trunc(Number.parseFloat(c.server.cvarByPointer(pointer)?.value ?? '') || 0),
690
+ get_pcvar_float: (c, [pointer]) => floatBits(Number.parseFloat(c.server.cvarByPointer(pointer)?.value ?? '') || 0),
691
+ set_pcvar_string: (c, [pointer, value]) => {
692
+ const cvar = c.server.cvarByPointer(pointer);
693
+ if (cvar) c.server.changeCvar(cvar, c.memory.text(value));
694
+ },
695
+ set_pcvar_num: (c, [pointer, value]) => {
696
+ const cvar = c.server.cvarByPointer(pointer);
697
+ if (cvar) c.server.changeCvar(cvar, String(value));
698
+ },
699
+ set_pcvar_float: (c, [pointer, value]) => {
700
+ const cvar = c.server.cvarByPointer(pointer);
701
+ if (cvar) c.server.changeCvar(cvar, cvarNumber(bitsFloat(value)));
702
+ },
703
+ get_cvar_string: (c, [name, buffer, length]) => c.memory.setText(buffer, length, cvarNamed(c, name)?.value ?? ''),
704
+ get_cvar_num: (c, [name]) => Math.trunc(Number.parseFloat(cvarNamed(c, name)?.value ?? '') || 0),
705
+ get_cvar_float: (c, [name]) => floatBits(Number.parseFloat(cvarNamed(c, name)?.value ?? '') || 0),
706
+ set_cvar_string: (c, [name, value]) => {
707
+ const v = cvarNamed(c, name);
708
+ if (v) c.server.changeCvar(v, c.memory.text(value));
709
+ },
710
+ set_cvar_num: (c, [name, value]) => {
711
+ const v = cvarNamed(c, name);
712
+ if (v) c.server.changeCvar(v, String(value));
713
+ },
714
+ set_cvar_float: (c, [name, value]) => {
715
+ const v = cvarNamed(c, name);
716
+ if (v) c.server.changeCvar(v, cvarNumber(bitsFloat(value)));
717
+ },
718
+ hook_cvar_change: (c, [pointer, callback]) => {
719
+ const cvar = c.server.cvarByPointer(pointer);
720
+ if (!cvar) return 0;
721
+ cvar.hooks.push(c.server.slotByPublic(c.memory.text(callback)));
722
+ return cvar.hooks.length;
723
+ },
724
+
725
+ // ------------------------------------------------------------ the game (reapi, engine, resemiclip)
726
+
727
+ register_dictionary: (c, [file]) => (c.server.loadDictionary(c.memory.text(file)) ? 1 : 0),
728
+ GetLangTransKey: (c, [key]) => c.server.langKey(c.memory.text(key)),
729
+ LookupLangKey: (c, [out, size, key, id]) => {
730
+ const text = c.server.lookupLang(c.memory.text(key), c.memory.cell(id));
731
+ if (text === undefined) return 0;
732
+ c.memory.setText(out, size, text);
733
+ return 1;
734
+ },
735
+ precache_model: (c, [name]) => c.server.precached.push(c.memory.text(name)),
736
+ get_member_game: (c, [member]) => c.server.rules.get(member) ?? 0,
737
+ set_member_game: (c, [member, at]) => {
738
+ c.server.rules.set(member, at ? c.memory.cell(at) : 0);
739
+ },
740
+ // CSGameRules::UpdateTeamScores: both scores set, and a TeamScore to everyone for each side.
741
+ rg_update_teamscores: (c, [cts, ts, add]) => {
742
+ const rules = c.server.rules;
743
+ const scores: [string, string, number][] = [['CT', 'm_iNumCTWins', cts], ['TERRORIST', 'm_iNumTerroristWins', ts]];
744
+ for (const [team, member, wins] of scores) {
745
+ const score = (add ? rules.get(constant(member)) ?? 0 : 0) + wins;
746
+ rules.set(constant(member), score);
747
+ c.server.userMessages.push({ name: 'TeamScore', player: 0, args: [team, score] });
748
+ }
749
+ return 1;
750
+ },
751
+ rg_round_end: (c, [delay, status, event, message, sound, trigger]) => {
752
+ c.server.roundEnds.push({
753
+ status,
754
+ event,
755
+ delay: bitsFloat(delay),
756
+ message: c.memory.text(message),
757
+ sound: c.memory.text(sound),
758
+ trigger: trigger !== 0,
759
+ });
760
+ return 1;
761
+ },
762
+ rg_send_audio: (c, [id, sample]) => {
763
+ c.server.sounds.push({ entity: id, sample: c.memory.text(sample) });
764
+ },
765
+ rh_emit_sound2: (c, [entity, _recipient, _channel, sample]) => {
766
+ c.server.sounds.push({ entity, sample: c.memory.text(sample) });
767
+ return 1;
768
+ },
769
+ emit_sound: (c, [entity, _channel, sample]) => {
770
+ c.server.sounds.push({ entity, sample: c.memory.text(sample) });
771
+ return 1;
772
+ },
773
+ rg_send_bartime: (c, [id, seconds]) => {
774
+ c.server.bartimes.set(id, seconds);
775
+ },
776
+ rg_join_team: (c, [id, team]) => {
777
+ const target = player(c, id);
778
+ if (target) target.team = TEAMS[team] as TeamName;
779
+ return 1;
780
+ },
781
+ // CBasePlayer::Observer_SetMode, as far as a test sees it: nothing for the
782
+ // mode he is in, "inEye" for a number past the modes, the first living
783
+ // player as the target - "roaming" when there is none - and the last mode.
784
+ rg_set_observer_mode: (c, [id, mode]) => {
785
+ const target = player(c, id);
786
+ if (!target || target.get('var_iuser1') === mode) return 1;
787
+ const asked = mode >= 1 && mode <= 6 ? mode : 4;
788
+ const watched = asked === 3 ? undefined : c.server.players.find(p => p !== target && p.alive);
789
+ target.set('var_iuser1', watched || asked === 3 ? asked : 3);
790
+ target.set('var_iuser2', watched?.id ?? 0);
791
+ target.set('m_iObserverLastMode', asked);
792
+ return 1;
793
+ },
794
+ resemiclip_take_control: (c, [take]) => {
795
+ c.server.semiclipControlled = take !== 0;
796
+ return 0;
797
+ },
798
+ resemiclip_set_user_mask: (c, [id, mask]) => {
799
+ c.server.semiclipMasks.set(id, mask | 0);
800
+ },
801
+ resemiclip_get_user_mask: (c, [id]) => c.server.semiclipMasks.get(id) ?? 0,
802
+ register_touch: (c, [touched, toucher, callback]) => {
803
+ c.server.touches.push({ touched: c.memory.text(touched), toucher: c.memory.text(toucher), slot: c.server.slotByPublic(c.memory.text(callback)) });
804
+ return c.server.touches.length;
805
+ },
806
+ // register_message: the callbacks server.sendMessage runs, by the message's id.
807
+ register_message: (c, [id, callback]) => {
808
+ const hooks = c.server.messageHooks.get(id) ?? [];
809
+ hooks.push(c.server.slotByPublic(c.memory.text(callback)));
810
+ c.server.messageHooks.set(id, hooks);
811
+ return hooks.length;
812
+ },
813
+ get_msg_args: c => hooked(c).args.length,
814
+ get_msg_argtype: (c, [arg]) => hooked(c).types[arg - 1] ?? 0,
815
+ get_msg_arg_int: (c, [arg]) => Math.trunc(Number(hooked(c).args[arg - 1] ?? 0)),
816
+ get_msg_arg_float: (c, [arg]) => floatBits(Number(hooked(c).args[arg - 1] ?? 0)),
817
+ get_msg_arg_string: (c, [arg, buffer, length]) => c.memory.setText(buffer, length, String(hooked(c).args[arg - 1] ?? '')),
818
+ set_msg_arg_int: (c, [arg, _type, value]) => setMessageArg(c, arg, value),
819
+ set_msg_arg_float: (c, [arg, _type, value]) => setMessageArg(c, arg, bitsFloat(value)),
820
+ set_msg_arg_string: (c, [arg, text]) => setMessageArg(c, arg, c.memory.text(text)),
821
+ register_srvcmd: (c, [name, callback]) => {
822
+ const slot = c.server.slotByPublic(c.memory.text(callback));
823
+ const command = c.memory.text(name).toLowerCase();
824
+ c.server.serverCommands.set(command, [...(c.server.serverCommands.get(command) ?? []), slot]);
825
+ return slot.index;
826
+ },
827
+ query_client_cvar: (c, [id, cvar, callback]) => {
828
+ c.server.cvarQueries.push({ player: id, cvar: c.memory.text(cvar), slot: c.server.slotByPublic(c.memory.text(callback)) });
829
+ },
830
+ is_entity: (c, [id]) => +c.server.entities.has(id),
831
+ get_user_info: (c, [id, key, buffer, length]) => c.memory.setText(buffer, length, player(c, id)?.info.get(c.memory.text(key)) ?? ''),
832
+ get_time: (c, [_format, buffer, length]) => c.memory.setText(buffer, length, '12:00 - 01.01.2026'),
833
+ get_weaponname: (c, [id, buffer, length]) => c.memory.setText(buffer, length, WEAPON_NAMES[id] ?? ''),
834
+ // SET_MODEL: the text, and the index of the precached model it names.
835
+ entity_set_model: (c, [id, model]) => {
836
+ const target = entity(c, id);
837
+ const path = c.memory.text(model);
838
+ target?.set('var_model', path);
839
+ target?.set('var_modelindex', c.server.precached.indexOf(path) + 1);
840
+ },
841
+ entity_set_size: (c, [id, mins, maxs]) => {
842
+ const target = entity(c, id);
843
+ const low = c.memory.vector(mins);
844
+ const high = c.memory.vector(maxs);
845
+ target?.set('var_mins', low);
846
+ target?.set('var_maxs', high);
847
+ target?.set('var_size', high.map((v, k) => v - low[k]));
848
+ },
849
+ // User messages: every name has an id, as on a server, and what is written
850
+ // between message_begin and message_end lands in server.userMessages.
851
+ get_user_msgid: (c, [name]) => {
852
+ const ids = c.server.messageIds;
853
+ const text = c.memory.text(name);
854
+ if (!ids.has(text)) ids.set(text, 100 + ids.size);
855
+ return ids.get(text)!;
856
+ },
857
+ message_begin: (c, [dest, type, origin, player]) => beginMessage(c, dest, type, player, [0, 1, 2].map(i => c.memory.cell(origin + i * 4))),
858
+ message_begin_f: (c, [dest, type, origin, player]) => beginMessage(c, dest, type, player, c.memory.vector(origin)),
859
+ write_byte: (c, [value]) => {
860
+ c.server.writing?.args.push(value);
861
+ return 1;
862
+ },
863
+ write_string: (c, [text]) => {
864
+ c.server.writing?.args.push(c.memory.text(text));
865
+ return 1;
866
+ },
867
+ message_end: (c) => {
868
+ const message = c.server.writing;
869
+ c.server.writing = null;
870
+ if (!message) return 1;
871
+ c.server.userMessages.push(message);
872
+ // SendAudio's byte is the player it plays to, its string the sound.
873
+ if (message.name === 'SendAudio') c.server.sounds.push({ entity: Number(message.args[0]), sample: String(message.args[1]) });
874
+ for (const listener of c.server.messageListeners) listener(message);
875
+ return 1;
876
+ },
877
+ write_coord_f: (c, [value]) => {
878
+ c.server.writing?.args.push(bitsFloat(value));
879
+ return 1;
880
+ },
881
+ // An entity's function run by a plugin (weapon.deploy()): kept in
882
+ // server.hamCalls; ExecuteHamB also runs the listeners of the entity's class.
883
+ ExecuteHam: (c, [fn, id, ...tail]) => executeHam(c, fn, id, tail, false),
884
+ ExecuteHamB: (c, [fn, id, ...tail]) => executeHam(c, fn, id, tail, true),
885
+ // A Ham Sandwich hook's argument and answer, on the running chain as reapi's are.
886
+ SetHamParamInteger: (c, [which, value]) => setHamParam(c, which, value),
887
+ SetHamParamEntity: (c, [which, value]) => setHamParam(c, which, value),
888
+ SetHamParamFloat: (c, [which, value]) => setHamParam(c, which, bitsFloat(value)),
889
+ SetHamParamString: (c, [which, text]) => setHamParam(c, which, c.memory.text(text)),
890
+ SetHamParamVector: (c, [which]) => setHamParam(c, which, 0),
891
+ SetHamReturnInteger: (c, [value]) => setHamReturn(c, value),
892
+ SetHamReturnEntity: (c, [value]) => setHamReturn(c, value),
893
+ SetHamReturnFloat: (c, [value]) => setHamReturn(c, bitsFloat(value)),
894
+ SetHamReturnString: (c, [text]) => setHamReturn(c, c.memory.text(text)),
895
+ SetHamReturnVector: c => setHamReturn(c, 0),
896
+ GetHamReturnInteger: (c, [out]) => c.memory.setCell(out, Number(chain(c).answer) | 0),
897
+ GetHamReturnEntity: (c, [out]) => c.memory.setCell(out, Number(chain(c).answer) | 0),
898
+ GetHamReturnFloat: (c, [out]) => c.memory.setFloat(out, Number(chain(c).answer)),
899
+ GetHamReturnString: (c, [out, size]) => c.memory.setText(out, size, String(chain(c).answer)),
900
+ GetHamReturnVector: (c, [out]) => c.memory.setVector(out, [0, 0, 0]),
901
+ set_dhudmessage: (c, [r, g, b, x, y, _effects, _fxtime, hold]) => {
902
+ c.server.hud = { color: [r, g, b], x: bitsFloat(x), y: bitsFloat(y), hold: bitsFloat(hold), channel: -1 };
903
+ },
904
+ CreateHudSyncObj: () => ++hudSyncObjects,
905
+ ShowSyncHudMsg: (c, [id, _sync, format, ...tail]) => sendText(c, id, 'hud', formatPawn(c, c.memory.text(format), tail)),
906
+ ClearSyncHud: () => 1,
907
+ };
908
+
909
+ // Natives that do here what another one does, under their own names.
910
+ const SAME_AS: Record<string, string> = {
911
+ module_exists: 'LibraryExists',
912
+ log_amx: 'server_print',
913
+ show_dhudmessage: 'show_hudmessage',
914
+ cs_get_user_deaths: 'get_user_deaths',
915
+ rg_give_item: 'give_item',
916
+ rg_remove_all_items: 'strip_user_weapons',
917
+ register_cvar: 'create_cvar',
918
+ precache_sound: 'precache_model',
919
+ precache_generic: 'precache_model',
920
+ write_angle_f: 'write_coord_f',
921
+ write_char: 'write_byte',
922
+ write_short: 'write_byte',
923
+ write_long: 'write_byte',
924
+ };
925
+ for (const [name, same] of Object.entries(SAME_AS)) NATIVES[name] = NATIVES[same];