@amxts/core 0.1.0 → 0.2.1

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 (77) hide show
  1. package/README.md +21 -25
  2. package/README.ru.md +22 -24
  3. package/as/amxts.d.ts +63 -3
  4. package/as/constants.ts +684 -116
  5. package/as/entities.ts +1905 -1364
  6. package/as/events.ts +1696 -907
  7. package/as/facade.ts +1240 -220
  8. package/as/fetch.ts +1519 -0
  9. package/as/flags.ts +159 -145
  10. package/as/hlds.ts +1071 -0
  11. package/as/hooks.ts +5226 -5379
  12. package/as/kit.ts +15 -5
  13. package/as/lib/check.ts +1 -1
  14. package/as/natives.ts +2658 -2499
  15. package/as/remote.ts +39 -15
  16. package/as/tsconfig.json +7 -2
  17. package/as/vector.ts +12 -9
  18. package/includes/order.txt +22 -29
  19. package/includes/sources.json +0 -8
  20. package/node_modules/assemblyscript/std/assembly/index.d.ts +20 -11
  21. package/package.json +29 -7
  22. package/runtime/deps/assemblyscript/dist/asc.js +86 -16
  23. package/runtime/deps/assemblyscript/dist/assemblyscript.js +29 -29
  24. package/runtime/licenses/MPL-2.0.txt +373 -0
  25. package/runtime/licenses/README.md +5 -1
  26. package/runtime/licenses/curl-COPYING +22 -0
  27. package/runtime/licenses/libssh2-COPYING +43 -0
  28. package/runtime/licenses/mbedTLS-LICENSE +553 -0
  29. package/runtime/natives.txt +108 -105
  30. package/runtime/patches/assemblyscript-0.28.20-amxts.patch +752 -296
  31. package/runtime/patches/wamr-2.4.5-amxts.patch +148 -0
  32. package/scripts/apply-docs.ts +2 -2
  33. package/scripts/auto-imports.ts +2 -2
  34. package/scripts/build-identity.ts +115 -0
  35. package/scripts/build-wasm.ts +101 -50
  36. package/scripts/check.ts +17 -3
  37. package/scripts/client-messages.ts +246 -0
  38. package/scripts/compile-cache.ts +4 -2
  39. package/scripts/compile-pool.ts +128 -0
  40. package/scripts/compile-worker.ts +23 -0
  41. package/scripts/compile.ts +156 -59
  42. package/scripts/docker-server.ts +13 -3
  43. package/scripts/docs/as/amxts.ts +86 -0
  44. package/scripts/docs/as/facade.ts +688 -188
  45. package/scripts/docs/as/fetch.ts +805 -0
  46. package/scripts/docs/entities.ts +43 -144
  47. package/scripts/docs/events.ts +17 -28
  48. package/scripts/docs/game.ts +68 -22
  49. package/scripts/docs/messages.ts +330 -16
  50. package/scripts/editor-api.ts +101 -0
  51. package/scripts/hlds-events.ts +370 -0
  52. package/scripts/includes.ts +12 -10
  53. package/scripts/player-fields.ts +16 -15
  54. package/scripts/prebuilt.ts +65 -23
  55. package/scripts/prepare.ts +42 -28
  56. package/scripts/project.ts +120 -20
  57. package/scripts/reapi-events.ts +98 -0
  58. package/scripts/shared-modules.ts +85 -23
  59. package/scripts/source-map.ts +242 -0
  60. package/scripts/system.ts +1 -1
  61. package/scripts/typed-commands.ts +263 -0
  62. package/scripts/typed-configs.ts +23 -12
  63. package/scripts/ui.ts +3 -1
  64. package/scripts/upgrade-menus.ts +274 -0
  65. package/scripts/upgrade-names.ts +384 -0
  66. package/scripts/upgrade.ts +857 -0
  67. package/src/cli-api.mjs +37 -4
  68. package/src/system.mjs +74 -3
  69. package/src/testing/compile.ts +8 -5
  70. package/src/testing/index.ts +2 -1
  71. package/src/testing/natives.ts +199 -5
  72. package/src/testing/network.ts +531 -0
  73. package/src/testing/server.ts +572 -48
  74. package/src/testing/setup.ts +1 -1
  75. package/src/testing/tables.ts +25 -0
  76. package/as/modules/http.ts +0 -184
  77. package/scripts/docs/as/modules/http.ts +0 -66
@@ -0,0 +1,370 @@
1
+ // What a server without reapi - plain HLDS: Valve's engine and game, Metamod
2
+ // and AMX Mod X's stock modules - has for each game event and game rules
3
+ // field of ReGameDLL's and ReHLDS's own, which reapi alone delivers.
4
+ //
5
+ // A heard fully: the same moment, the same fields, preventDefault() and the
6
+ // answer where reapi takes them;
7
+ // B heard, but late, without preventDefault() or the answer, or with
8
+ // fewer fields - `gaps` says which, on the event's tooltip and the hooks
9
+ // page;
10
+ // C nothing hears it (NOT_HEARD, with the reason): a listener is one line
11
+ // in the console, and a project with `target: "hlds"` does not build it
12
+ // (scripts/reapi-events.ts).
13
+ //
14
+ // An A or B event's backend is `<event>Hlds` in as/hlds.ts, which
15
+ // scripts/generate-hooks.ts registers on the event's first listener, once,
16
+ // when the server has no reapi; `post` adds `<event>PostHlds` for the first
17
+ // listener after the game.
18
+ import type { Text } from './docs/events';
19
+
20
+ export interface HeardEvent {
21
+ class: 'A' | 'B';
22
+ /** The stock hook that hears it. */
23
+ backend: string;
24
+ /** What plain HLDS does not give: B only. */
25
+ gaps?: Text;
26
+ /** A backend of its own for the listeners after the game. */
27
+ post?: boolean;
28
+ }
29
+
30
+ const after: Text = {
31
+ en: 'heard after the game has acted, so `preventDefault()` and changing a field do nothing',
32
+ ru: 'слышно, когда игра уже сделала своё, поэтому `preventDefault()` и запись поля ничего не делают',
33
+ };
34
+
35
+ /** `after`, then more. */
36
+ function afterAnd(en: string, ru: string): Text {
37
+ return { en: `${after.en}; ${en}`, ru: `${after.ru}; ${ru}` };
38
+ }
39
+
40
+ function noAnswer(en: string, ru: string): Text {
41
+ return { en: `${en}; returning an answer does nothing`, ru: `${ru}; ответ ничего не делает` };
42
+ }
43
+
44
+ function buy(en: string, ru: string): Text {
45
+ return noAnswer(
46
+ `heard through cstrike's \`CS_OnBuy\`: \`preventDefault()\` stops the purchase; ${en}`,
47
+ `слышно через \`CS_OnBuy\` модуля cstrike: \`preventDefault()\` отменяет покупку; ${ru}`,
48
+ );
49
+ }
50
+
51
+ function thrown(en: string, ru: string): Text {
52
+ return afterAnd(`it is heard as the grenade gets its model${en}`, `слышно, когда граната получает модель${ru}`);
53
+ }
54
+
55
+ const precached: Text = {
56
+ en: '`preventDefault()` skips the precache, which answers 0; returning an answer or changing `file` does nothing',
57
+ ru: '`preventDefault()` пропускает прекэш, и он отвечает 0; ответ и запись `file` ничего не делают',
58
+ };
59
+
60
+ export const HEARD: Record<string, HeardEvent> = {
61
+ // The round.
62
+ newRound: {
63
+ class: 'B',
64
+ backend: 'HLTV message (1=0 2=0) before, decal_reset.sc playback at its time after; the map\'s first round is the game\'s restart as it commences',
65
+ post: true,
66
+ gaps: {
67
+ en: 'heard as the round restarts - the listeners before the game when it announces the round, the ones after it once its players have respawned - but `preventDefault()` does nothing',
68
+ ru: 'слышно при перезапуске раунда — обработчики «до» игры, когда она объявляет раунд, обработчики «после» — когда её игроки возродились, — но `preventDefault()` ничего не делает',
69
+ },
70
+ },
71
+ roundStart: { class: 'B', backend: 'logevent World triggered "Round_Start"', gaps: after },
72
+ roundEnd: {
73
+ class: 'B',
74
+ backend: 'logevent World triggered "Round_End"; winner and reason from the TextMsg, the trigger log line or SendAudio before it',
75
+ gaps: afterAnd(
76
+ 'returning an answer does nothing; `delay` is the original game\'s 5 seconds, 3 for `"gameCommence"`, unless `game.endRound` set it',
77
+ 'ответ ничего не делает; `delay` — 5 секунд оригинальной игры, 3 для `"gameCommence"`, если его не задал `game.endRound`',
78
+ ),
79
+ },
80
+ gameThink: { class: 'B', backend: 'server_frame', gaps: after },
81
+ mapReset: { class: 'B', backend: 'the decal_reset.sc playback CleanUpMap ends with', gaps: after },
82
+ intermission: { class: 'B', backend: 'register_event "30" (SVC_INTERMISSION)', gaps: after },
83
+ changeLevel: { class: 'B', backend: 'server_changelevel', gaps: { en: '`preventDefault()` does nothing', ru: '`preventDefault()` ничего не делает' } },
84
+
85
+ // Spawning and dying.
86
+ playerSpawn: { class: 'B', backend: 'Ham_Spawn post on "player", alive', gaps: after },
87
+ playerKilled: {
88
+ class: 'B',
89
+ backend: 'Ham_Killed post on "player"; the inflictor is the victim\'s dmg_inflictor',
90
+ gaps: after,
91
+ },
92
+ deathNotice: {
93
+ class: 'B',
94
+ backend: 'DeathMsg message',
95
+ gaps: {
96
+ en: 'heard as its death message is sent: `preventDefault()` does nothing, and `inflictor` reads as the world',
97
+ ru: 'слышно, когда уходит сообщение о смерти: `preventDefault()` ничего не делает, а `inflictor` читается как мир',
98
+ },
99
+ },
100
+ sendDeathMessage: {
101
+ class: 'B',
102
+ backend: 'DeathMsg message',
103
+ gaps: {
104
+ en: '`preventDefault()` stops the message; `assister` and `inflictor` read as the world, `flags` as empty, `rarity` has `"headshot"` alone; changing a field does nothing',
105
+ ru: '`preventDefault()` отменяет сообщение; `assister` и `inflictor` читаются как мир, `flags` пуст, в `rarity` бывает только `"headshot"`; запись поля ничего не делает',
106
+ },
107
+ },
108
+ pain: { class: 'A', backend: 'FM_EmitSound of a player\'s pain sound; lastHitGroup and hasArmour from the player' },
109
+ deathSound: { class: 'A', backend: 'FM_EmitSound of a player\'s death sound' },
110
+
111
+ // The bomb and the VIP: the game's log lines.
112
+ giveBomb: { class: 'B', backend: 'logevent "Spawned_With_The_Bomb"', gaps: noAnswer(after.en, after.ru) },
113
+ becomeBomber: { class: 'B', backend: 'logevent "Spawned_With_The_Bomb"', gaps: noAnswer(after.en, after.ru) },
114
+ becomeVip: { class: 'B', backend: 'logevent "Became_VIP"', gaps: after },
115
+ plantBomb: {
116
+ class: 'B',
117
+ backend: 'FM_SetModel w_c4.mdl on a grenade: its planter, place and velocity are set',
118
+ gaps: afterAnd('it is heard as the bomb gets its model; returning an answer does nothing', 'слышно, когда бомба получает модель; ответ ничего не делает'),
119
+ },
120
+ defuseBombStart: { class: 'B', backend: 'logevent "Begin_Bomb_Defuse_With_Kit" / "_Without_Kit"', gaps: after },
121
+ defuseBombEnd: {
122
+ class: 'B',
123
+ backend: 'logevent "Defused_The_Bomb"',
124
+ gaps: afterAnd('heard only when the bomb is defused, not when a defuse stops halfway', 'слышно, только когда бомба обезврежена, а не когда разминирование прервано'),
125
+ },
126
+ explodeBomb: {
127
+ class: 'B',
128
+ backend: 'logevent Team "TERRORIST" triggered "Target_Bombed"',
129
+ gaps: afterAnd('`trace` and `damageType` read as 0', '`trace` и `damageType` читаются как 0'),
130
+ },
131
+
132
+ // Money and buying.
133
+ addMoney: {
134
+ class: 'B',
135
+ backend: 'Money message: the change from the last amount the player was sent',
136
+ gaps: afterAnd(
137
+ '`amount` is how much his money moved since the game last sent it to him, and `reason` reads as `"none"`',
138
+ '`amount` — на сколько сдвинулись его деньги с тех пор, как игра в последний раз их ему прислала, а `reason` читается как `"none"`',
139
+ ),
140
+ },
141
+ buyWeapon: { class: 'B', backend: 'cstrike CS_OnBuy, a weapon', gaps: buy('`event.result` reads as `null`', '`event.result` читается как `null`') },
142
+ buyItem: { class: 'B', backend: 'cstrike CS_OnBuy, the equipment', gaps: buy('heard for the equipment menu\'s items', 'слышно для предметов меню снаряжения') },
143
+ buyAmmo: { class: 'B', backend: 'cstrike CS_OnBuy, ammo', gaps: buy('`weapon` reads as the world, `blinkMoney` as `true`', '`weapon` читается как мир, `blinkMoney` — как `true`') },
144
+ itemRestricted: {
145
+ class: 'B',
146
+ backend: 'cstrike CS_OnBuyAttempt',
147
+ gaps: {
148
+ en: 'asked only for `"buying"`, through cstrike\'s `CS_OnBuyAttempt`: answering `true` forbids the purchase, `false` lets the game go on',
149
+ ru: 'спрашивается только про `"buying"`, через `CS_OnBuyAttempt` модуля cstrike: ответ `true` запрещает покупку, `false` оставляет решение игре',
150
+ },
151
+ },
152
+
153
+ // Teams, menus and commands.
154
+ chooseTeam: {
155
+ class: 'B',
156
+ backend: 'client commands jointeam, and menuselect in the team menu',
157
+ gaps: {
158
+ en: 'heard from the player\'s command: `preventDefault()` stops it; returning an answer does nothing, and a team the game picks itself is not heard',
159
+ ru: 'слышно по команде игрока: `preventDefault()` её отменяет; ответ ничего не делает, а команда, которую игра выбирает сама, не слышна',
160
+ },
161
+ },
162
+ chooseAppearance: {
163
+ class: 'B',
164
+ backend: 'client commands joinclass, and menuselect in the class menu',
165
+ gaps: {
166
+ en: 'heard from the player\'s command: `preventDefault()` stops it; a model the game picks itself is not heard',
167
+ ru: 'слышно по команде игрока: `preventDefault()` её отменяет; модель, которую игра выбирает сама, не слышна',
168
+ },
169
+ },
170
+ dropPlayerItem: {
171
+ class: 'B',
172
+ backend: 'client command drop',
173
+ gaps: {
174
+ en: 'heard from the player\'s `drop` command: `preventDefault()` stops it; `event.result` reads as `null`, and a drop the game makes itself is not heard',
175
+ ru: 'слышно по команде игрока `drop`: `preventDefault()` её отменяет; `event.result` читается как `null`, а выброс, который игра делает сама, не слышен',
176
+ },
177
+ },
178
+ showVguiMenu: {
179
+ class: 'B',
180
+ backend: 'VGUIMenu message',
181
+ gaps: {
182
+ en: 'heard as the menu is sent, to a player with VGUI menus on (not a bot): `preventDefault()` stops it; `oldMenu` reads as `""`, and changing a field does nothing',
183
+ ru: 'слышно, когда меню уходит игроку с включёнными VGUI-меню (не боту): `preventDefault()` его отменяет; `oldMenu` читается как `""`, запись поля ничего не делает',
184
+ },
185
+ },
186
+
187
+ // Clients.
188
+ clientConnected: { class: 'B', backend: 'client_connect', gaps: after },
189
+ connectClient: { class: 'B', backend: 'client_connect', gaps: afterAnd('heard once the player is let in', 'слышно, когда игрока уже пустили') },
190
+ disconnectClient: {
191
+ class: 'B',
192
+ backend: 'client_disconnected',
193
+ gaps: afterAnd('`crash` reads as `false`, and `reason` is the one AMX Mod X was told', '`crash` читается как `false`, а `reason` — причина, которую узнал AMX Mod X'),
194
+ },
195
+ userInfoChange: { class: 'B', backend: 'client_infochanged', gaps: afterAnd('`info` reads as `""`', '`info` читается как `""`') },
196
+ changeName: {
197
+ class: 'B',
198
+ backend: 'client_infochanged, when the name in the info differs from the player\'s',
199
+ gaps: noAnswer(afterAnd('`info` reads as `""`', '`info` читается как `""`').en, afterAnd('`info` reads as `""`', '`info` читается как `""`').ru),
200
+ },
201
+
202
+ // Items, sounds, effects.
203
+ playerBlind: {
204
+ class: 'B',
205
+ backend: 'Ham_CS_Player_Blind',
206
+ gaps: afterAnd('`inflictor` and `attacker` read as the world, `color` as zero', '`inflictor` и `attacker` читаются как мир, `color` — нулевым'),
207
+ },
208
+ playerGotWeapon: { class: 'B', backend: 'Ham_AddPlayerItem post, when the item was added', gaps: after },
209
+ canPlayerHearPlayer: {
210
+ class: 'B',
211
+ backend: 'FM_Voice_SetClientListening',
212
+ gaps: {
213
+ en: 'asked as the game tells the engine who hears whom, with `sv_alltalk` on too, and for a player who muted the other: the answer overrides both',
214
+ ru: 'спрашивается, когда игра сообщает движку, кто кого слышит, — и при включённом `sv_alltalk`, и для игрока, заглушившего другого: ответ перекрывает и то и другое',
215
+ },
216
+ },
217
+ throwHeGrenade: { class: 'B', backend: 'FM_SetModel w_hegrenade.mdl', gaps: thrown(', and `eventIndex` reads as 0', ', а `eventIndex` читается как 0') },
218
+ throwFlashbang: { class: 'B', backend: 'FM_SetModel w_flashbang.mdl', gaps: thrown('', '') },
219
+ throwSmokeGrenade: { class: 'B', backend: 'FM_SetModel w_smokegrenade.mdl', gaps: thrown(', and `eventIndex` reads as 0', ', а `eventIndex` читается как 0') },
220
+ throwGrenade: { class: 'B', backend: 'FM_SetModel of a thrown grenade', gaps: thrown(', and `eventIndex` reads as 0', ', а `eventIndex` читается как 0') },
221
+ setModel: {
222
+ class: 'B',
223
+ backend: 'FM_SetModel on a weaponbox',
224
+ gaps: { en: '`preventDefault()` keeps the model off; changing `modelName` does nothing', ru: '`preventDefault()` не даёт модели встать; запись `modelName` ничего не делает' },
225
+ },
226
+ gibSpawn: { class: 'B', backend: 'FM_SetModel on a gib', gaps: after },
227
+ bounceGibTouch: { class: 'A', backend: 'engine touch of a gib' },
228
+ startSound: {
229
+ class: 'B',
230
+ backend: 'FM_EmitSound',
231
+ gaps: {
232
+ en: 'heard for the sounds the game plays through the engine\'s `EmitSound`: `preventDefault()` stops it; `recipients` reads as 0, and changing a field does nothing',
233
+ ru: 'слышно для звуков, которые игра проигрывает через `EmitSound` движка: `preventDefault()` его отменяет; `recipients` читается как 0, запись поля ничего не делает',
234
+ },
235
+ },
236
+
237
+ // Precaching: fakemeta's forwards, added at plugin_precache, before the game's own.
238
+ precacheModel: { class: 'B', backend: 'FM_PrecacheModel', gaps: precached },
239
+ precacheSound: { class: 'B', backend: 'FM_PrecacheSound', gaps: precached },
240
+ precacheFile: { class: 'B', backend: 'FM_PrecacheGeneric', gaps: precached },
241
+ };
242
+
243
+ /**
244
+ * The events nothing on plain HLDS hears, with the reason: ReGameDLL's own
245
+ * function, one the game calls inside another with no hook between, or the
246
+ * engine's own, which Metamod does not see.
247
+ */
248
+ export const NOT_HEARD: Record<string, string> = {
249
+ activateServer: 'the engine activates the map before plugin_init: a stock hook added then never hears it',
250
+ addMultiDamage: 'a global function of the game, called inside an attack; no stock hook',
251
+ addResource: 'the engine\'s own resource list',
252
+ airAccelerate: 'player movement inside the game\'s PM_Move; no stock hook',
253
+ airMove: 'player movement inside the game\'s PM_Move; no stock hook',
254
+ applyMultiDamage: 'a global function of the game, called inside an attack; no stock hook',
255
+ balanceTeams: 'game rules code inside the round\'s restart; no stock hook',
256
+ canHaveItem: 'a game rules virtual: Ham Sandwich hooks entities only',
257
+ canSwitchTeam: 'ReGameDLL\'s own function',
258
+ checkMapConditions: 'game rules code at the map\'s start, before the plugins',
259
+ checkTimeBasedDamage: 'inside the player\'s PreThink; no stock hook',
260
+ checkUserInfo: 'the engine\'s own check of a connecting client',
261
+ checkWaterJump: 'player movement inside the game\'s PM_Move; no stock hook',
262
+ checkWinConditions: 'a game rules virtual: Ham Sandwich hooks entities only',
263
+ clearMultiDamage: 'a global function of the game, called inside an attack; no stock hook',
264
+ consoleMessage: 'the engine\'s own console text to a client',
265
+ createWeaponBox: 'a weaponbox is heard only as it gets its model (setModel); its arguments are gone by then',
266
+ dropWeaponsOnDeath: 'a game rules virtual: Ham Sandwich hooks entities only',
267
+ defaultDeploy: 'a non-virtual helper inside a weapon\'s Deploy: the deploy event is the weapon\'s',
268
+ defaultReload: 'a non-virtual helper inside a weapon\'s Reload: the reload event is the weapon\'s',
269
+ defaultShotgunReload: 'a non-virtual helper inside a shotgun\'s Reload',
270
+ disappear: 'ReGameDLL\'s own function',
271
+ dropIdlePlayer: 'game code inside the player\'s think; no stock hook',
272
+ dropShield: 'a non-virtual function of the player; no stock hook',
273
+ selectSpawnPoint: 'a non-virtual function of the player; no stock hook',
274
+ serverCommand: 'the engine\'s own command buffer',
275
+ explodeFlashbang: 'a non-virtual function of the grenade; its think is heard, not the step that explodes',
276
+ explodeHeGrenade: 'a non-virtual function of the grenade; its think is heard, not the step that explodes',
277
+ explodeSmokeGrenade: 'a non-virtual function of the grenade; its think is heard, not the step that explodes',
278
+ shootBuckshot: 'a non-virtual function inside a weapon\'s attack',
279
+ fireBullets: 'a non-virtual function inside a weapon\'s attack',
280
+ shoot: 'a non-virtual function inside a weapon\'s attack',
281
+ fallDamage: 'a game rules virtual: Ham Sandwich hooks entities only',
282
+ canRespawn: 'a game rules virtual: Ham Sandwich hooks entities only',
283
+ canTakeDamage: 'a game rules virtual: Ham Sandwich hooks entities only',
284
+ shouldSwitchWeapon: 'a game rules virtual: Ham Sandwich hooks entities only',
285
+ getForceCamera: 'a global function of the game; no stock hook',
286
+ getIntoGame: 'a non-virtual function of the player; no stock hook',
287
+ getNextBestWeapon: 'a game rules virtual: Ham Sandwich hooks entities only',
288
+ spawnSpot: 'a game rules virtual: Ham Sandwich hooks entities only',
289
+ giveDefaultItems: 'inside the game rules\' PlayerSpawn, for a player who did not survive and a map without game_player_equip; no stock hook',
290
+ giveItem: 'a non-virtual function of the player; fakemeta\'s CreateNamedEntity hears every entity made by name',
291
+ giveShield: 'a non-virtual function of the player; no stock hook',
292
+ hintMessage: 'ReGameDLL\'s own function; the original queues hints',
293
+ canShootThrough: 'ReGameDLL\'s own function',
294
+ joiningThink: 'a non-virtual function of the player; no stock hook',
295
+ recoil: 'a non-virtual function inside a weapon\'s attack',
296
+ ladderMove: 'player movement inside the game\'s PM_Move; no stock hook',
297
+ move: 'the game\'s PM_Move: fakemeta has no forward for it',
298
+ spectateNext: 'a non-virtual function of the player; no stock hook',
299
+ canSpectate: 'a non-virtual function of the player; no stock hook',
300
+ observerSetMode: 'a non-virtual function of the player, which the game calls itself; the specmode command is one of its callers',
301
+ observerThink: 'a non-virtual function of the player; no stock hook',
302
+ gameEvent: 'the bot manager\'s own events',
303
+ spawnEquip: 'ReGameDLL\'s own function',
304
+ playerDeathThink: 'a non-virtual function inside the player\'s PreThink',
305
+ playStepSound: 'player movement inside the game\'s PM_Move; the client plays the steps',
306
+ duckMovement: 'player movement inside the game\'s PM_Move; no stock hook',
307
+ jumpMovement: 'player movement inside the game\'s PM_Move; no stock hook',
308
+ printf: 'the engine\'s own console',
309
+ radio: 'a non-virtual function of the player; its SendAudio goes to each teammate, and to nobody without one',
310
+ removeAllItems: 'a non-virtual function of the player; no stock hook',
311
+ removeGuns: 'game rules code inside the round\'s restart; no stock hook',
312
+ removeSpawnProtection: 'ReGameDLL\'s own function',
313
+ resetSequenceInfo: 'a non-virtual function of an animating entity',
314
+ chatMessage: 'ReGameDLL\'s own function; the say command and the SayText message are the plugin\'s to hear',
315
+ serverDeactivate: 'happens as the plugins unload; the end event is the plugin\'s',
316
+ setAnimation: 'a non-virtual function of the player; no stock hook',
317
+ changeModel: 'ReGameDLL\'s own function',
318
+ setSpawnProtection: 'ReGameDLL\'s own function',
319
+ showMenu: 'its ShowMenu message is AMX Mod X\'s menus\' too: a hook of it hears every plugin\'s menu',
320
+ spawnHeadGib: 'a global function of the game; no stock hook',
321
+ spawnRandomGibs: 'a global function of the game; no stock hook',
322
+ startDeathCam: 'a non-virtual function of the player; no stock hook',
323
+ startSpectating: 'a non-virtual function of the player; no stock hook',
324
+ switchTeam: 'a non-virtual function of the player; its TeamInfo message is every team change\'s',
325
+ takeDamageImpulse: 'ReGameDLL\'s own function',
326
+ teamFull: 'a game rules virtual: Ham Sandwich hooks entities only',
327
+ teamStacked: 'a game rules virtual: Ham Sandwich hooks entities only',
328
+ traceLine: 'a trace inside the flashbang\'s explosion',
329
+ unDuck: 'player movement inside the game\'s PM_Move; no stock hook',
330
+ updateStatusBar: 'a non-virtual function of the player; no stock hook',
331
+ useEmpty: 'a non-virtual function of the player; no stock hook',
332
+ waitTillLand: 'a gib\'s think: Ham Sandwich cannot hook the gib\'s class',
333
+ waterJump: 'player movement inside the game\'s PM_Move; no stock hook',
334
+ };
335
+
336
+ /** A game rules field of ReGameDLL's own, read on plain HLDS from what the server does have. */
337
+ export interface HeardField {
338
+ class: 'A' | 'B';
339
+ backend: string;
340
+ gaps?: Text;
341
+ }
342
+
343
+ export const HEARD_FIELDS: Record<string, HeardField> = {
344
+ gameName: { class: 'A', backend: 'fakemeta: GetGameDescription read, and answered with the text written' },
345
+ timeLimit: {
346
+ class: 'B',
347
+ backend: 'mp_timelimit and AMX Mod X\'s time left',
348
+ gaps: {
349
+ en: 'counted from `mp_timelimit` and AMX Mod X\'s time left, which starts when a restart is announced; writing it sets `mp_timelimit`',
350
+ ru: 'считается из `mp_timelimit` и оставшегося времени AMX Mod X, которое идёт с объявления рестарта; запись меняет `mp_timelimit`',
351
+ },
352
+ },
353
+ gameStartTime: {
354
+ class: 'B',
355
+ backend: 'mp_timelimit and AMX Mod X\'s time left',
356
+ gaps: {
357
+ en: 'counted from AMX Mod X\'s time left, which starts when a restart is announced; `0` while `mp_timelimit` is `0`, and writing it does nothing',
358
+ ru: 'считается из оставшегося времени AMX Mod X, которое идёт с объявления рестарта; `0`, пока `mp_timelimit` равен `0`, запись ничего не делает',
359
+ },
360
+ },
361
+ maxPlayers: { class: 'B', backend: 'get_maxplayers', gaps: { en: 'the server\'s slots; writing it does nothing', ru: 'слоты сервера; запись ничего не делает' } },
362
+ };
363
+
364
+ export const FIELDS_NOT_HEARD: Record<string, string> = {
365
+ teamBalanced: 'ReGameDLL\'s own state of the round\'s balance',
366
+ neededPlayers: 'ReGameDLL\'s own flag; the original keeps it in no member the gamedata has',
367
+ skipShowMenu: 'ReGameDLL\'s own option',
368
+ escapeRatio: 'ReGameDLL\'s own count of the escaped terrorists',
369
+ updateInterval: 'the voice manager\'s own timer, which the gamedata does not have',
370
+ };
@@ -4,7 +4,7 @@
4
4
  * standard set; the core's includes/ carries its own - order.txt and the
5
5
  * contracts of the Pawn plugins the official modules stand in for - and
6
6
  * includes/vendor/ the third-party ones the API is generated from (ReAPI and
7
- * the rehlds constants, easy_http, resemiclip), which `bun run setup` fetches
7
+ * the rehlds constants, resemiclip), which `bun run setup` fetches
8
8
  * as includes/sources.json pins them.
9
9
  *
10
10
  * A project looks in its own folders first: its includes/ (the includes of
@@ -22,6 +22,7 @@ import { realpathSync } from 'node:fs';
22
22
  import { join, resolve } from 'node:path';
23
23
  import process from 'node:process';
24
24
  import { fileURLToPath } from 'node:url';
25
+ import { serverFolder } from '../src/system.mjs';
25
26
  import { existsSync, readdirSync, readFileSync } from './tracked-fs';
26
27
 
27
28
  const CORE = resolve(fileURLToPath(new URL('..', import.meta.url)));
@@ -37,9 +38,14 @@ function same(a: string, b: string): boolean {
37
38
  }
38
39
  }
39
40
 
40
- /** The server's own includes: addons/amxmodx/scripting/include beside AMXTS_SERVER (addons/amxts), when it is there. */
41
+ /** The server's own includes: addons/amxmodx/scripting/include beside its addons/amxts (AMXTS_SERVER), when it is there. */
41
42
  export function serverIncludes(): string | null {
42
- const server = process.env.AMXTS_SERVER;
43
+ let server: string;
44
+ try {
45
+ server = serverFolder(process.env.AMXTS_SERVER ?? '');
46
+ } catch {
47
+ return null;
48
+ }
43
49
  if (!server) return null;
44
50
  const dir = resolve(server, '..', 'amxmodx', 'scripting', 'include');
45
51
  return existsSync(dir) ? dir : null;
@@ -86,13 +92,9 @@ export function readInclude(name: string): string {
86
92
 
87
93
  /**
88
94
  * Parses includes/order.txt. A bare name is included and (by the host
89
- * generator) pulled; a line starting with `-` is a deny marker — the name
90
- * still has to resolve (reapi.inc unconditionally #includes reapi_vtc,
91
- * reapi_reunion and reapi_rechecker, so they must be on disk to compile it),
92
- * but it is excluded from whatever build the caller pulls into a native
93
- * table. A #include costs nothing; a native reference does, because AMX Mod X
94
- * refuses to load a plugin whose native table names a module the server does
95
- * not have loaded.
95
+ * generator) pulled; a line starting with `-` is a deny marker - the name
96
+ * still has to resolve (an include that #includes it needs it on disk), but
97
+ * it is excluded from whatever build the caller pulls into a native table.
96
98
  */
97
99
  export function parseOrder(content: string): { includes: string[]; denied: Set<string> } {
98
100
  const lines = content
@@ -3,8 +3,8 @@
3
3
  // A plugin declares its fields as TypeScript does, by augmenting the facade's
4
4
  // Player:
5
5
  //
6
- // import "~/facade";
7
- // declare module "~/facade" {
6
+ // import "@amxts/core";
7
+ // declare module "@amxts/core" {
8
8
  // interface Player {
9
9
  // spawnProtected: boolean;
10
10
  // glow: {
@@ -29,7 +29,8 @@
29
29
  // name held in a string, so the build does both:
30
30
  //
31
31
  // - reading a file (`read`), it records the members of every
32
- // `interface Player` in a `declare module "~/facade"` block and blanks the
32
+ // `interface Player` in a `declare module "@amxts/core"` block (the
33
+ // facade, `~/facade` once the specifiers are the tree's) and blanks the
33
34
  // block out - spaces, so every line and column after it stays where it was.
34
35
  // An interface of the same file that a field is typed with becomes
35
36
  // `import { Name } from "~/facade";` (and `export { Name };` if it was
@@ -56,13 +57,13 @@
56
57
  // A field whose name Player already has - `solid` is Entity's var_solid - is
57
58
  // refused: it would silently replace the engine's property.
58
59
  //
59
- // A change is an event, `"playerchange"`, which the module raises on every
60
+ // A change is an event, `"playerChange"`, which the module raises on every
60
61
  // write that changes a value (runtime/src/module.cpp). With
61
62
  // `{ field: "spawnProtected" }` its listener gets the field's own event class, whose `value` and `previous`
62
63
  // have the field's type - one per field, per object member and per object
63
64
  // field, made here. The compiler binds an event's type to the name in the
64
65
  // call (`K extends keyof ServerEventMap`), and the field is in the options, so
65
- // the call's name becomes `"playerchange:spawnProtected"` and ServerEventMap is given
66
+ // the call's name becomes `"playerChange:spawnProtected"` and ServerEventMap is given
66
67
  // that key; `PlayerChangeEvent<"spawnProtected">` written as a type becomes the class.
67
68
 
68
69
  // @ts-ignore - shipped as JavaScript, with types beside it we do not need here
@@ -107,10 +108,10 @@ const HOLDER = '__AmxtsPlayerFields';
107
108
  const PLAYER_LIST = '__AmxtsPlayerList';
108
109
 
109
110
  /** The event a field's change is, and the facade's class for it. */
110
- const CHANGE_EVENT = 'playerchange';
111
+ const CHANGE_EVENT = 'playerChange';
111
112
  const CHANGE_CLASS = 'PlayerChangeEvent';
112
113
 
113
- /** The interface in that file whose members - `"playerchange:spawnProtected": ...` - go into ServerEventMap. */
114
+ /** The interface in that file whose members - `"playerChange:spawnProtected": ...` - go into ServerEventMap. */
114
115
  const CHANGES = '__AmxtsPlayerChanges';
115
116
 
116
117
  const DECLARE_MODULE = /\bdeclare\s+module\s*(["'])~\/facade\1\s*\{/g;
@@ -134,7 +135,7 @@ export function playerFieldsBuild() {
134
135
  const open = match.index! + match[0].length - 1;
135
136
  const close = closingBrace(text, open);
136
137
  if (close < 0) {
137
- problems.push(`${file}:${lineOf(text, match.index!)}: declare module "~/facade" - no closing brace`);
138
+ problems.push(`${file}:${lineOf(text, match.index!)}: declare module "@amxts/core" - no closing brace`);
138
139
  continue;
139
140
  }
140
141
  blocks.push({ start: match.index!, open, close });
@@ -181,7 +182,7 @@ export function playerFieldsBuild() {
181
182
  const rest = body.search(/\S/);
182
183
  if (rest >= 0) {
183
184
  const what = body.slice(rest).trim().split('\n')[0].trim();
184
- problems.push(`${file}:${lineOf(text, from + rest)}: declare module "~/facade" - only \`interface Player { ... }\` goes here, not "${what}"`);
185
+ problems.push(`${file}:${lineOf(text, from + rest)}: declare module "@amxts/core" - only \`interface Player { ... }\` goes here, not "${what}"`);
185
186
  }
186
187
  }
187
188
 
@@ -228,7 +229,7 @@ export function playerFieldsBuild() {
228
229
  const player = classes.get('Player');
229
230
  if (!player) {
230
231
  const first = [...fields.values()][0];
231
- throw new Error(`${first.where}: Player.${first.name} - the facade's Player is not part of this compile; import "~/facade"`);
232
+ throw new Error(`${first.where}: Player.${first.name} - the facade's Player is not part of this compile; import "@amxts/core"`);
232
233
  }
233
234
 
234
235
  // Player's members and those of every class it extends.
@@ -477,9 +478,9 @@ function changeEvents(fields: PlayerField[]): ChangeEvent[] {
477
478
  }
478
479
 
479
480
  /**
480
- * A `"playerchange"` listener's `{ field: "spawnProtected" }` and a
481
+ * A `"playerChange"` listener's `{ field: "spawnProtected" }` and a
481
482
  * `PlayerChangeEvent<"spawnProtected">` written as a type, turned into the field's own
482
- * event class: the call's name becomes `"playerchange:spawnProtected"`, the key
483
+ * event class: the call's name becomes `"playerChange:spawnProtected"`, the key
483
484
  * ServerEventMap is given for it, and the type the class. A field the compile
484
485
  * has no declaration of is a problem, and so is one not written out.
485
486
  */
@@ -489,7 +490,7 @@ function typeChangeListeners(parser: any, classes: Map<string, string>): string[
489
490
  const known = (field: string, node: any) => {
490
491
  if (classes.has(field)) return true;
491
492
  const names = [...classes.keys()].map(name => `"${name}"`).join(', ');
492
- problems.push(`${where(node)}: playerchange - "${field}" is not a field of Player in what this plugin imports (${names || 'it imports none'})`);
493
+ problems.push(`${where(node)}: playerChange - "${field}" is not a field of Player in what this plugin imports (${names || 'it imports none'})`);
493
494
  return false;
494
495
  };
495
496
 
@@ -498,7 +499,7 @@ function typeChangeListeners(parser: any, classes: Map<string, string>): string[
498
499
  walk(source.statements, (node) => {
499
500
  if (node.kind === asc.NodeKind.Call) {
500
501
  const field = listenedField(node);
501
- if (field === null) problems.push(`${where(node)}: playerchange - the field is written out, as the event's name is: { field: "spawnProtected" }`);
502
+ if (field === null) problems.push(`${where(node)}: playerChange - the field is written out, as the event's name is: { field: "spawnProtected" }`);
502
503
  else if (field !== undefined && known(field, node)) node.args[0].value = `${CHANGE_EVENT}:${field}`;
503
504
  return;
504
505
  }
@@ -515,7 +516,7 @@ function isStringLiteral(node: any): boolean {
515
516
  return node?.kind === asc.NodeKind.Literal && node.literalKind === asc.LiteralKind.String;
516
517
  }
517
518
 
518
- /** The field a `"playerchange"` listener names in its options; undefined for any other call, null when not written out. */
519
+ /** The field a `"playerChange"` listener names in its options; undefined for any other call, null when not written out. */
519
520
  function listenedField(call: any): string | null | undefined {
520
521
  const method = call.expression.kind === asc.NodeKind.PropertyAccess ? call.expression.property.text : '';
521
522
  const [type, , options] = call.args;