@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.
- package/README.md +21 -25
- package/README.ru.md +22 -24
- package/as/amxts.d.ts +63 -3
- package/as/constants.ts +684 -116
- package/as/entities.ts +1905 -1364
- package/as/events.ts +1696 -907
- package/as/facade.ts +1240 -220
- package/as/fetch.ts +1519 -0
- package/as/flags.ts +159 -145
- package/as/hlds.ts +1071 -0
- package/as/hooks.ts +5226 -5379
- package/as/kit.ts +15 -5
- package/as/lib/check.ts +1 -1
- package/as/natives.ts +2658 -2499
- package/as/remote.ts +39 -15
- package/as/tsconfig.json +7 -2
- package/as/vector.ts +12 -9
- package/includes/order.txt +22 -29
- package/includes/sources.json +0 -8
- package/node_modules/assemblyscript/std/assembly/index.d.ts +20 -11
- package/package.json +29 -7
- package/runtime/deps/assemblyscript/dist/asc.js +86 -16
- package/runtime/deps/assemblyscript/dist/assemblyscript.js +29 -29
- package/runtime/licenses/MPL-2.0.txt +373 -0
- package/runtime/licenses/README.md +5 -1
- package/runtime/licenses/curl-COPYING +22 -0
- package/runtime/licenses/libssh2-COPYING +43 -0
- package/runtime/licenses/mbedTLS-LICENSE +553 -0
- package/runtime/natives.txt +108 -105
- package/runtime/patches/assemblyscript-0.28.20-amxts.patch +752 -296
- package/runtime/patches/wamr-2.4.5-amxts.patch +148 -0
- package/scripts/apply-docs.ts +2 -2
- package/scripts/auto-imports.ts +2 -2
- package/scripts/build-identity.ts +115 -0
- package/scripts/build-wasm.ts +101 -50
- package/scripts/check.ts +17 -3
- package/scripts/client-messages.ts +246 -0
- package/scripts/compile-cache.ts +4 -2
- package/scripts/compile-pool.ts +128 -0
- package/scripts/compile-worker.ts +23 -0
- package/scripts/compile.ts +156 -59
- package/scripts/docker-server.ts +13 -3
- package/scripts/docs/as/amxts.ts +86 -0
- package/scripts/docs/as/facade.ts +688 -188
- package/scripts/docs/as/fetch.ts +805 -0
- package/scripts/docs/entities.ts +43 -144
- package/scripts/docs/events.ts +17 -28
- package/scripts/docs/game.ts +68 -22
- package/scripts/docs/messages.ts +330 -16
- package/scripts/editor-api.ts +101 -0
- package/scripts/hlds-events.ts +370 -0
- package/scripts/includes.ts +12 -10
- package/scripts/player-fields.ts +16 -15
- package/scripts/prebuilt.ts +65 -23
- package/scripts/prepare.ts +42 -28
- package/scripts/project.ts +120 -20
- package/scripts/reapi-events.ts +98 -0
- package/scripts/shared-modules.ts +85 -23
- package/scripts/source-map.ts +242 -0
- package/scripts/system.ts +1 -1
- package/scripts/typed-commands.ts +263 -0
- package/scripts/typed-configs.ts +23 -12
- package/scripts/ui.ts +3 -1
- package/scripts/upgrade-menus.ts +274 -0
- package/scripts/upgrade-names.ts +384 -0
- package/scripts/upgrade.ts +857 -0
- package/src/cli-api.mjs +37 -4
- package/src/system.mjs +74 -3
- package/src/testing/compile.ts +8 -5
- package/src/testing/index.ts +2 -1
- package/src/testing/natives.ts +199 -5
- package/src/testing/network.ts +531 -0
- package/src/testing/server.ts +572 -48
- package/src/testing/setup.ts +1 -1
- package/src/testing/tables.ts +25 -0
- package/as/modules/http.ts +0 -184
- 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
|
+
};
|
package/scripts/includes.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
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
|
|
90
|
-
* still has to resolve (
|
|
91
|
-
*
|
|
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
|
package/scripts/player-fields.ts
CHANGED
|
@@ -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 "
|
|
7
|
-
// declare module "
|
|
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 "
|
|
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, `"
|
|
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 `"
|
|
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 = '
|
|
111
|
+
const CHANGE_EVENT = 'playerChange';
|
|
111
112
|
const CHANGE_CLASS = 'PlayerChangeEvent';
|
|
112
113
|
|
|
113
|
-
/** The interface in that file whose members - `"
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 `"
|
|
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 `"
|
|
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)}:
|
|
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)}:
|
|
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 `"
|
|
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;
|