@amxts/core 0.1.0 → 0.2.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.
- package/README.md +18 -23
- package/README.ru.md +19 -22
- package/as/amxts.d.ts +63 -3
- package/as/constants.ts +684 -116
- package/as/entities.ts +1905 -1364
- package/as/events.ts +1694 -905
- 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 +6 -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-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 +15 -26
- 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 +39 -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 +830 -0
- package/src/cli-api.mjs +37 -4
- package/src/system.mjs +74 -3
- package/src/testing/compile.ts +8 -5
- package/src/testing/natives.ts +199 -5
- package/src/testing/network.ts +531 -0
- package/src/testing/server.ts +565 -47
- package/src/testing/tables.ts +25 -0
- package/as/modules/http.ts +0 -184
- package/scripts/docs/as/modules/http.ts +0 -66
package/as/hlds.ts
ADDED
|
@@ -0,0 +1,1071 @@
|
|
|
1
|
+
// The game events and game rules fields of ReGameDLL's and ReHLDS's own,
|
|
2
|
+
// which reapi alone delivers, on a server without it - plain HLDS - through
|
|
3
|
+
// the stock modules: AMX Mod X's logevents and messages, Ham Sandwich,
|
|
4
|
+
// fakemeta, cstrike, a client's commands. scripts/hlds-events.ts says, for
|
|
5
|
+
// each, what plain HLDS gives and what it does not.
|
|
6
|
+
//
|
|
7
|
+
// `<event>Hlds(fire, hook)` is registered once, on the event's first listener,
|
|
8
|
+
// when the server has no reapi (as/hooks.ts), and everything it registers is
|
|
9
|
+
// switched by `hook`: off while the event has no listener, so that what it
|
|
10
|
+
// hears does not reach the plugin for nothing. It makes the event with the fields
|
|
11
|
+
// the stock hook knows - by the argument's place in reapi's hookchain, as the
|
|
12
|
+
// event's getters read it - and hands it to the listeners before the game and
|
|
13
|
+
// to those after it: a stock hook mostly hears the game once it has acted,
|
|
14
|
+
// so both run then. What a listener asks of the game - preventDefault(), an
|
|
15
|
+
// answer, a field written - reaches it where the stock hook can take it, and
|
|
16
|
+
// is one line in the console where it cannot (`settle`).
|
|
17
|
+
import {
|
|
18
|
+
Call, CellBuffer, Player, TouchEvent, game, server, handled, arg, argText, cellFloat, floatCell,
|
|
19
|
+
__Switch, __ham, __off, __onCell, __outcome, __sayOnce, __switchedPublic, __whenPrecache, __whenUp
|
|
20
|
+
} from "./facade";
|
|
21
|
+
import { Entity } from "./entities";
|
|
22
|
+
import {
|
|
23
|
+
HookEvent, AddMoneyEvent, BounceGibTouchEvent, BuyAmmoEvent, BuyItemEvent, BuyWeaponEvent,
|
|
24
|
+
CanPlayerHearPlayerEvent, ChangeLevelEvent, MapResetEvent, ChooseAppearanceEvent, ChooseTeamEvent, ClientConnectedEvent,
|
|
25
|
+
UserInfoChangeEvent, ConnectClientEvent, DeathNoticeEvent, DeathSoundEvent, DefuseBombEndEvent,
|
|
26
|
+
DefuseBombStartEvent, DisconnectClientEvent, DropPlayerItemEvent, ExplodeBombEvent, GameThinkEvent, GibSpawnEvent,
|
|
27
|
+
GiveBombEvent, IntermissionEvent, ItemRestrictedEvent, BecomeBomberEvent, BecomeVipEvent, RoundStartEvent,
|
|
28
|
+
PainEvent, PlantBombEvent, PlayerBlindEvent, PlayerGotWeaponEvent, PlayerKilledEvent, PlayerSpawnEvent,
|
|
29
|
+
PrecacheFileEvent, PrecacheModelEvent, PrecacheSoundEvent, NewRoundEvent, RoundEndEvent,
|
|
30
|
+
SendDeathMessageEvent, ChangeNameEvent, SetModelEvent, ShowVguiMenuEvent, StartSoundEvent,
|
|
31
|
+
ThrowFlashbangEvent, ThrowGrenadeEvent, ThrowHeGrenadeEvent, ThrowSmokeGrenadeEvent
|
|
32
|
+
} from "./hooks";
|
|
33
|
+
import {
|
|
34
|
+
GetHamReturnInteger, NATIVE_dllfunc, NATIVE_engfunc, NATIVE_forward_return, NATIVE_register_event,
|
|
35
|
+
NATIVE_register_logevent, cs_get_user_money, find_ent_by_model, get_cvar_float, get_ent_data,
|
|
36
|
+
get_gametime, get_maxplayers, get_orig_retval, get_timeleft, get_user_info, get_user_name,
|
|
37
|
+
get_user_userid, is_user_alive, is_user_connected, pev_valid, read_argv, read_logargv, read_logdata, register_clcmd, register_forward,
|
|
38
|
+
set_cvar_float,
|
|
39
|
+
} from "./natives";
|
|
40
|
+
import {
|
|
41
|
+
CSI_DEFUSER, CSI_FLASHBANG, CSI_HEGRENADE, CSI_LAST_WEAPON, CSI_NVGS, CSI_PRIAMMO, CSI_SECAMMO, CSI_SHIELD,
|
|
42
|
+
CSI_SHIELDGUN, CSI_SMOKEGRENADE, CSI_VEST, CSI_VESTHELM, DLLFunc_GetGameDescription, EngFunc_PrecacheEvent,
|
|
43
|
+
EngFunc_SetClientListening, FMRES_IGNORED, FMRES_SUPERCEDE, FMV_CELL, FMV_STRING, FM_EmitSound,
|
|
44
|
+
FM_GetGameDescription, FM_PrecacheGeneric, FM_PrecacheModel, FM_PrecacheSound, FM_SetModel,
|
|
45
|
+
FM_Voice_SetClientListening, Ham_AddPlayerItem, Ham_CS_Player_Blind, Ham_Killed, Ham_Spawn,
|
|
46
|
+
ITEM_ASSAULT, ITEM_DEFUSEKIT, ITEM_KEVLAR, ITEM_NVG, ITEM_SHIELDGUN, ITEM_TYPE_BUYING,
|
|
47
|
+
ROUND_ALL_HOSTAGES_RESCUED, ROUND_BOMB_DEFUSED, ROUND_CTS_PREVENT_ESCAPE, ROUND_CTS_WIN, ROUND_END_DRAW,
|
|
48
|
+
ROUND_ESCAPING_TERRORISTS_NEUTRALIZED, ROUND_GAME_COMMENCE, ROUND_HOSTAGE_NOT_RESCUED, ROUND_NONE,
|
|
49
|
+
ROUND_TARGET_BOMB, ROUND_TARGET_SAVED, ROUND_TERRORISTS_ESCAPED, ROUND_TERRORISTS_NOT_ESCAPED,
|
|
50
|
+
ROUND_TERRORISTS_WIN, ROUND_VIP_ASSASSINATED, ROUND_VIP_ESCAPED, ROUND_VIP_NOT_ESCAPED, WINSTATUS_CTS,
|
|
51
|
+
PLUGIN_HANDLED, WINSTATUS_DRAW, WINSTATUS_NONE, WINSTATUS_TERRORISTS
|
|
52
|
+
} from "./constants";
|
|
53
|
+
import {
|
|
54
|
+
TextMsgMessage, SendAudioMessage, MoneyMessage, DeathMsgMessage, VGUIMenuMessage, CS_OnBuyEvent, CS_OnBuyAttemptEvent,
|
|
55
|
+
ClientConnectEvent, ClientDisconnectedEvent, ClientInfochangedEvent, ClientPutinserverEvent, ServerChangelevelEvent,
|
|
56
|
+
ServerFrameEvent, ServerMessageMap, addServerListener, removeServerListener
|
|
57
|
+
} from "./events";
|
|
58
|
+
|
|
59
|
+
/** The listeners of one event, phase by phase: as/hooks.ts's `<event>FireHlds`. */
|
|
60
|
+
type Fire<E> = (event: E, post: bool) => void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* What the listeners asked of the game that this backend cannot give it: one
|
|
64
|
+
* line in the console for the event. `block` is what the stock hook answers to
|
|
65
|
+
* stop the game's function, -1 where it cannot; `answers` that the backend
|
|
66
|
+
* gave the game the listener's answer itself.
|
|
67
|
+
*/
|
|
68
|
+
function settle(event: HookEvent, name: string, block: i32 = -1, answers: bool = false): void {
|
|
69
|
+
if (event.__prevented && block >= 0) __outcome(block);
|
|
70
|
+
const lost = (event.__prevented && block < 0) || (event.__answered && !answers) || event.__changed;
|
|
71
|
+
if (lost) __sayOnce(`${name} is heard on this server without ReAPI, where the game does not take a listener's preventDefault(), answer or change of a field`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The listeners before the game, then the ones after it, on one event; then what they asked of the game. */
|
|
75
|
+
function hear<E>(fire: Fire<E>, event: E, name: string, block: i32 = -1): void {
|
|
76
|
+
fire(event, false);
|
|
77
|
+
fire(event, true);
|
|
78
|
+
settle(changetype<HookEvent>(event), name, block);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A player's index, or 0 for anything else. */
|
|
82
|
+
function playerOr0(id: i32): i32 {
|
|
83
|
+
return id >= 1 && id <= get_maxplayers() && is_user_connected(id) != 0 ? id : 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------- the facade's own listeners
|
|
87
|
+
|
|
88
|
+
/** A server event's listener of a backend, taken off while `hook` is off. */
|
|
89
|
+
function onServer<E>(listener: (event: E) => void, hook: __Switch): void {
|
|
90
|
+
addServerListener<E>(listener);
|
|
91
|
+
hook.add((on: bool): void => {
|
|
92
|
+
if (on) addServerListener<E>(listener);
|
|
93
|
+
else removeServerListener<E>(listener);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A message's listener of a backend, taken off while `hook` is off. */
|
|
98
|
+
function onMessage<K extends keyof ServerMessageMap>(name: K, listener: (event: ServerMessageMap[K]) => void, hook: __Switch): void {
|
|
99
|
+
server.addMessageListener<K>(name, listener);
|
|
100
|
+
hook.add((on: bool): void => {
|
|
101
|
+
if (on) server.addMessageListener<K>(name, listener);
|
|
102
|
+
else server.removeMessageListener<K>(name, listener);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The listeners of one stock hook several events' backends share - a model
|
|
108
|
+
* given, a sound played: each backend's is there while its event is listened
|
|
109
|
+
* for, and the hook is switched off while none is.
|
|
110
|
+
*/
|
|
111
|
+
class Hearers<H> {
|
|
112
|
+
list: H[] = [];
|
|
113
|
+
private hook: __Switch = new __Switch();
|
|
114
|
+
private hooked: bool = false;
|
|
115
|
+
|
|
116
|
+
/** Adds a backend's hearer, switched by its `listened`; `register` hooks the stock hook on the first. */
|
|
117
|
+
add(hearer: H, listened: __Switch, register: (hook: __Switch) => void): void {
|
|
118
|
+
if (!this.hooked) {
|
|
119
|
+
this.hooked = true;
|
|
120
|
+
register(this.hook);
|
|
121
|
+
}
|
|
122
|
+
this.list.push(hearer);
|
|
123
|
+
listened.add((on: bool): void => {
|
|
124
|
+
if (on) this.list.push(hearer);
|
|
125
|
+
else this.list.splice(this.list.indexOf(hearer), 1);
|
|
126
|
+
this.hook.set(this.list.length > 0);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------- the game's log
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Calls `handler` on a line of the game's log that AMX Mod X's logevent
|
|
135
|
+
* filter takes: `argc` arguments, `filter` such as "1=Round_End".
|
|
136
|
+
*/
|
|
137
|
+
function onLog(key: string, argc: i32, filter: string, handler: () => void, hook: __Switch): void {
|
|
138
|
+
__whenUp((): void => {
|
|
139
|
+
const pub = __switchedPublic((a: number, b: number, c: number, d: number): void => {
|
|
140
|
+
if (heardTwice(key)) return;
|
|
141
|
+
handler();
|
|
142
|
+
}, `hlds:log:${key}`, hook);
|
|
143
|
+
if (pub.length > 0) new Call(NATIVE_register_logevent).str(pub).num(argc).str(filter).run();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// AMX Mod X walks its logevents with the line it parsed last. A line logged
|
|
148
|
+
// while a logevent runs - the "Round_End" of the kills a listener of
|
|
149
|
+
// "Round_Start" makes - is heard, and then again by every logevent after
|
|
150
|
+
// the outer one as its walk goes on. Each logevent hears a line once a frame.
|
|
151
|
+
const lastLines = new Map<string, string>();
|
|
152
|
+
|
|
153
|
+
function heardTwice(key: string): bool {
|
|
154
|
+
const line = `${get_gametime()} ${read_logdata()}`;
|
|
155
|
+
if (lastLines.has(key) && lastLines.get(key) == line) return true;
|
|
156
|
+
lastLines.set(key, line);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The player a log line names first - `"Name<userid><authid><team>"` - or 0. */
|
|
161
|
+
function loggedPlayer(): i32 {
|
|
162
|
+
const text = read_logargv(0);
|
|
163
|
+
// The name may hold `<` itself: the user id is the third group from the end.
|
|
164
|
+
const team = text.lastIndexOf("<");
|
|
165
|
+
const authid = team > 0 ? text.lastIndexOf("<", team - 1) : -1;
|
|
166
|
+
const userid = authid > 0 ? text.lastIndexOf("<", authid - 1) : -1;
|
|
167
|
+
if (userid < 0) return 0;
|
|
168
|
+
const wanted = I32.parseInt(text.slice(userid + 1, authid - 1));
|
|
169
|
+
const max = get_maxplayers();
|
|
170
|
+
for (let id = 1; id <= max; id++) {
|
|
171
|
+
if (is_user_connected(id) != 0 && get_user_userid(id) == wanted) return id;
|
|
172
|
+
}
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** A player's line of the log - `"..." triggered "<what>"`: the player, 0 when he is gone. */
|
|
177
|
+
function onPlayerLog(event: string, what: string, handler: (id: i32) => void, hook: __Switch): void {
|
|
178
|
+
onLog(event, 3, `2=${what}`, (): void => handler(loggedPlayer()), hook);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The planted bomb: the grenade with the C4's model, or 0. */
|
|
182
|
+
function plantedBomb(): i32 {
|
|
183
|
+
return max(<i32>find_ent_by_model(-1, "grenade", "models/w_c4.mdl"), 0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------- fakemeta
|
|
187
|
+
|
|
188
|
+
/** Hooks a fakemeta function: `handler` reads its arguments with arg() and argText(). */
|
|
189
|
+
function onFakemeta(fn: i32, post: bool, key: string, handler: () => void, hook: __Switch | null, early: bool = false): void {
|
|
190
|
+
const register = (): void => {
|
|
191
|
+
const pub = __switchedPublic((a: number, b: number, c: number, d: number): void => handler(), `hlds:fm:${key}`, hook, FMRES_IGNORED);
|
|
192
|
+
if (pub.length > 0) register_forward(fn, pub, post ? 1 : 0);
|
|
193
|
+
};
|
|
194
|
+
if (early) __whenPrecache(register);
|
|
195
|
+
else __whenUp(register);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// One SetModel hook for every event that hears an entity get its model: each
|
|
199
|
+
// looks at it and takes its own.
|
|
200
|
+
const modelHearers = new Hearers<(entity: i32, model: string) => void>();
|
|
201
|
+
|
|
202
|
+
function onSetModel(hearer: (entity: i32, model: string) => void, hook: __Switch): void {
|
|
203
|
+
modelHearers.add(hearer, hook, (shared: __Switch): void => onFakemeta(FM_SetModel, false, "setmodel", heardModel, shared));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function heardModel(): void {
|
|
207
|
+
const entity = <i32>arg(0);
|
|
208
|
+
const model = argText(1);
|
|
209
|
+
const hearers = modelHearers.list.slice(0);
|
|
210
|
+
for (let i = 0; i < hearers.length; i++) hearers[i](entity, model);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// One EmitSound hook for every event that hears a sound.
|
|
214
|
+
const soundHearers = new Hearers<(entity: i32, sample: string) => void>();
|
|
215
|
+
|
|
216
|
+
function onSound(hearer: (entity: i32, sample: string) => void, hook: __Switch): void {
|
|
217
|
+
soundHearers.add(hearer, hook, (shared: __Switch): void => onFakemeta(FM_EmitSound, false, "emitsound", heardSound, shared));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function heardSound(): void {
|
|
221
|
+
const entity = <i32>arg(0);
|
|
222
|
+
const sample = argText(2);
|
|
223
|
+
const hearers = soundHearers.list.slice(0);
|
|
224
|
+
for (let i = 0; i < hearers.length; i++) hearers[i](entity, sample);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---------------------------------------------------------------- client commands
|
|
228
|
+
|
|
229
|
+
/** Hears a client command before the game: `handler` may block it with handled(). */
|
|
230
|
+
function onCommand(event: string, command: string, handler: (id: i32) => void, hook: __Switch): void {
|
|
231
|
+
__whenUp((): void => {
|
|
232
|
+
const pub = __switchedPublic((a: number, b: number, c: number, d: number): void => handler(<i32>a), `hlds:${event}:${command}`, hook);
|
|
233
|
+
if (pub.length > 0) register_clcmd(command, pub);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Hears one of the game's messages as AMX Mod X's events do: `flags` and `conditions` as register_event takes them. */
|
|
238
|
+
function onEvent(message: string, handler: () => void, hook: __Switch, flags: string, conditions: string[] = []): void {
|
|
239
|
+
__whenUp((): void => {
|
|
240
|
+
const pub = __switchedPublic((a: number, b: number, c: number, d: number): void => handler(), `hlds:event:${message}`, hook);
|
|
241
|
+
if (pub.length == 0) return;
|
|
242
|
+
const call = new Call(NATIVE_register_event).str(message).str(pub).str(flags);
|
|
243
|
+
for (let i = 0; i < conditions.length; i++) call.str(conditions[i]);
|
|
244
|
+
call.run();
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** A command's first argument as a number; 0 for none. */
|
|
249
|
+
function commandNumber(): i32 {
|
|
250
|
+
const text = read_argv(1);
|
|
251
|
+
return text.length > 0 ? I32.parseInt(text) : 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------- the round
|
|
255
|
+
|
|
256
|
+
// The game time of this round's HLTV message: the players respawned after it
|
|
257
|
+
// at that same time, when the map's decals are reset.
|
|
258
|
+
let newRoundAt: f64 = -1;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* A new round: the HLTV message the game sends when it restarts a round,
|
|
262
|
+
* before the players respawn. A map's first round is a restart too - the
|
|
263
|
+
* game commences once both sides have players.
|
|
264
|
+
*/
|
|
265
|
+
export function newRoundHlds(fire: Fire<NewRoundEvent>, hook: __Switch): void {
|
|
266
|
+
onEvent("HLTV", (): void => announceRound(fire), hook, "a", ["1=0", "2=0"]);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function announceRound(fire: Fire<NewRoundEvent>): void {
|
|
270
|
+
const now = get_gametime();
|
|
271
|
+
if (now == newRoundAt) return;
|
|
272
|
+
newRoundAt = now;
|
|
273
|
+
const event = new NewRoundEvent();
|
|
274
|
+
fire(event, false);
|
|
275
|
+
settle(event, "newRound");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// The map's decals reset: the game's CleanUpMap plays events/decal_reset.sc
|
|
279
|
+
// as a round restarts, after its players have respawned, and nothing else
|
|
280
|
+
// does. The engine's playback forward is heard for that one event alone,
|
|
281
|
+
// compared in the module: it fires on every shot.
|
|
282
|
+
const decalHearers = new Hearers<() => void>();
|
|
283
|
+
|
|
284
|
+
function onDecalsReset(hearer: () => void, hook: __Switch): void {
|
|
285
|
+
decalHearers.add(hearer, hook, (shared: __Switch): void => __whenUp((): void => {
|
|
286
|
+
const decals = <i32>new Call(NATIVE_engfunc).num(EngFunc_PrecacheEvent).ref(1).str("events/decal_reset.sc").run();
|
|
287
|
+
__onCell("pfn_playbackevent", decalsReset.index, 2, decals);
|
|
288
|
+
shared.add((on: bool): void => {
|
|
289
|
+
if (on) __onCell("pfn_playbackevent", decalsReset.index, 2, decals);
|
|
290
|
+
else __off("pfn_playbackevent", decalsReset.index);
|
|
291
|
+
});
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function decalsReset(a: i32): void {
|
|
296
|
+
const hearers = decalHearers.list.slice(0);
|
|
297
|
+
for (let i = 0; i < hearers.length; i++) hearers[i]();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The round once its players have respawned: the decals' reset at its HLTV message's time. */
|
|
301
|
+
export function newRoundPostHlds(fire: Fire<NewRoundEvent>, hook: __Switch): void {
|
|
302
|
+
onDecalsReset((): void => {
|
|
303
|
+
if (get_gametime() != newRoundAt) return;
|
|
304
|
+
const event = new NewRoundEvent();
|
|
305
|
+
fire(event, true);
|
|
306
|
+
settle(event, "newRound");
|
|
307
|
+
}, hook);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** The map cleaned up for a new round: the decals' reset, its last step. */
|
|
311
|
+
export function mapResetHlds(fire: Fire<MapResetEvent>, hook: __Switch): void {
|
|
312
|
+
onDecalsReset((): void => hear(fire, new MapResetEvent(), "mapReset"), hook);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** The freeze time is over: the game logs "Round_Start". */
|
|
316
|
+
export function roundStartHlds(fire: Fire<RoundStartEvent>, hook: __Switch): void {
|
|
317
|
+
onLog("roundStart", 2, "1=Round_Start", (): void => hear(fire, new RoundStartEvent(), "roundStart"), hook);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// The round's end, told before its log line, by the time each came: the
|
|
321
|
+
// centred message the game sends - "#Terrorists_Win" - the log line of the
|
|
322
|
+
// side that won, or of the world - "Terrorists_Win" - and the radio sound.
|
|
323
|
+
let endText = "";
|
|
324
|
+
let endTextAt: f64 = -1;
|
|
325
|
+
let endTrigger = "";
|
|
326
|
+
let endTriggerAt: f64 = -1;
|
|
327
|
+
let endSound = "";
|
|
328
|
+
let endSoundAt: f64 = -1;
|
|
329
|
+
|
|
330
|
+
/** A round's end as the game announces it: its message, and the reason and the winner it is. */
|
|
331
|
+
class Ending {
|
|
332
|
+
constructor(public text: string, public reason: i32, public winner: i32) {}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const ENDINGS: Ending[] = [
|
|
336
|
+
new Ending("#Target_Bombed", ROUND_TARGET_BOMB, WINSTATUS_TERRORISTS),
|
|
337
|
+
new Ending("#VIP_Escaped", ROUND_VIP_ESCAPED, WINSTATUS_CTS),
|
|
338
|
+
new Ending("#VIP_Assassinated", ROUND_VIP_ASSASSINATED, WINSTATUS_TERRORISTS),
|
|
339
|
+
new Ending("#Terrorists_Escaped", ROUND_TERRORISTS_ESCAPED, WINSTATUS_TERRORISTS),
|
|
340
|
+
new Ending("#CTs_PreventEscape", ROUND_CTS_PREVENT_ESCAPE, WINSTATUS_CTS),
|
|
341
|
+
new Ending("#Escaping_Terrorists_Neutralized", ROUND_ESCAPING_TERRORISTS_NEUTRALIZED, WINSTATUS_CTS),
|
|
342
|
+
new Ending("#Bomb_Defused", ROUND_BOMB_DEFUSED, WINSTATUS_CTS),
|
|
343
|
+
new Ending("#CTs_Win", ROUND_CTS_WIN, WINSTATUS_CTS),
|
|
344
|
+
new Ending("#Terrorists_Win", ROUND_TERRORISTS_WIN, WINSTATUS_TERRORISTS),
|
|
345
|
+
new Ending("#Round_Draw", ROUND_END_DRAW, WINSTATUS_DRAW),
|
|
346
|
+
new Ending("#All_Hostages_Rescued", ROUND_ALL_HOSTAGES_RESCUED, WINSTATUS_CTS),
|
|
347
|
+
new Ending("#Target_Saved", ROUND_TARGET_SAVED, WINSTATUS_CTS),
|
|
348
|
+
new Ending("#Hostages_Not_Rescued", ROUND_HOSTAGE_NOT_RESCUED, WINSTATUS_TERRORISTS),
|
|
349
|
+
new Ending("#Terrorists_Not_Escaped", ROUND_TERRORISTS_NOT_ESCAPED, WINSTATUS_CTS),
|
|
350
|
+
new Ending("#VIP_Not_Escaped", ROUND_VIP_NOT_ESCAPED, WINSTATUS_TERRORISTS),
|
|
351
|
+
new Ending("#Game_Commencing", ROUND_GAME_COMMENCE, WINSTATUS_DRAW),
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
/** The ending a text names, or null. */
|
|
355
|
+
function endingOf(text: string): Ending | null {
|
|
356
|
+
for (let i = 0; i < ENDINGS.length; i++) {
|
|
357
|
+
if (ENDINGS[i].text == text) return ENDINGS[i];
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* The round's end: the game logs "Round_End" after its message, its log line
|
|
364
|
+
* of who won and its sound, which say who won and why - the message, or
|
|
365
|
+
* where a plugin changed it the log line, then the sound. The delay is the
|
|
366
|
+
* game's when the round is already ending (as game.endRound leaves it), or
|
|
367
|
+
* the original game's: 3 seconds for "Game Commencing", 5 for every other end.
|
|
368
|
+
*/
|
|
369
|
+
export function roundEndHlds(fire: Fire<RoundEndEvent>, hook: __Switch): void {
|
|
370
|
+
onMessage("text", (message: TextMsgMessage): void => {
|
|
371
|
+
if (message.destination != "center" || !message.text.startsWith("#")) return;
|
|
372
|
+
endText = message.text;
|
|
373
|
+
endTextAt = get_gametime();
|
|
374
|
+
}, hook);
|
|
375
|
+
onMessage("sound", (message: SendAudioMessage): void => {
|
|
376
|
+
if (message.sender != null || !message.sound.startsWith("%!MRAD_")) return;
|
|
377
|
+
endSound = message.sound;
|
|
378
|
+
endSoundAt = get_gametime();
|
|
379
|
+
}, hook);
|
|
380
|
+
onLog("roundEnd:team", 6, "0=Team", (): void => {
|
|
381
|
+
endTrigger = `#${read_logargv(3)}`;
|
|
382
|
+
endTriggerAt = get_gametime();
|
|
383
|
+
}, hook);
|
|
384
|
+
onLog("roundEnd:world", 4, "0=World triggered", (): void => {
|
|
385
|
+
endTrigger = `#${read_logargv(1)}`;
|
|
386
|
+
endTriggerAt = get_gametime();
|
|
387
|
+
}, hook);
|
|
388
|
+
onLog("roundEnd", 2, "1=Round_End", (): void => {
|
|
389
|
+
const now = get_gametime();
|
|
390
|
+
let ending = endTextAt == now ? endingOf(endText) : null;
|
|
391
|
+
if (ending == null && endTriggerAt == now) ending = endingOf(endTrigger);
|
|
392
|
+
const reason = ending != null ? ending.reason : ROUND_NONE;
|
|
393
|
+
const winner = ending != null ? ending.winner : endSoundAt == now ? soundWinner(endSound) : WINSTATUS_NONE;
|
|
394
|
+
const terminating = game.roundEnding && game.newRoundTime > now;
|
|
395
|
+
const delay = terminating ? game.newRoundTime - now : reason == ROUND_GAME_COMMENCE ? 3.0 : 5.0;
|
|
396
|
+
const event = new RoundEndEvent();
|
|
397
|
+
event.__give(0, winner);
|
|
398
|
+
event.__give(1, reason);
|
|
399
|
+
event.__give(2, <i32>floatCell(delay));
|
|
400
|
+
event.__give(-1, 1);
|
|
401
|
+
hear(fire, event, "roundEnd");
|
|
402
|
+
}, hook);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function soundWinner(sound: string): i32 {
|
|
406
|
+
if (sound == "%!MRAD_terwin") return WINSTATUS_TERRORISTS;
|
|
407
|
+
if (sound == "%!MRAD_ctwin") return WINSTATUS_CTS;
|
|
408
|
+
if (sound == "%!MRAD_rounddraw") return WINSTATUS_DRAW;
|
|
409
|
+
return WINSTATUS_NONE;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** The game rules think once a frame: the server's frame. */
|
|
413
|
+
export function gameThinkHlds(fire: Fire<GameThinkEvent>, hook: __Switch): void {
|
|
414
|
+
onServer<ServerFrameEvent>((frame: ServerFrameEvent): void => hear(fire, new GameThinkEvent(), "gameThink"), hook);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** The map is over: the game sends SVC_INTERMISSION, which AMX Mod X's events hear as "30". */
|
|
418
|
+
export function intermissionHlds(fire: Fire<IntermissionEvent>, hook: __Switch): void {
|
|
419
|
+
onEvent("30", (): void => hear(fire, new IntermissionEvent(), "intermission"), hook, "a");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** The game changes the map: AMX Mod X's server_changelevel. */
|
|
423
|
+
export function changeLevelHlds(fire: Fire<ChangeLevelEvent>, hook: __Switch): void {
|
|
424
|
+
onServer<ServerChangelevelEvent>((change: ServerChangelevelEvent): void => hear(fire, new ChangeLevelEvent(), "changeLevel"), hook);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ---------------------------------------------------------------- spawning and dying
|
|
428
|
+
|
|
429
|
+
/** A player spawned into the round: Ham Sandwich's Spawn after the game, of a player alive - not one who only joined. */
|
|
430
|
+
export function playerSpawnHlds(fire: Fire<PlayerSpawnEvent>, hook: __Switch): void {
|
|
431
|
+
__ham(Ham_Spawn, "player", (id: number, b: number, c: number, d: number): void => {
|
|
432
|
+
if (is_user_alive(<i32>id) == 0) return;
|
|
433
|
+
const event = new PlayerSpawnEvent();
|
|
434
|
+
event.__give(0, <i32>id);
|
|
435
|
+
hear(fire, event, "playerSpawn");
|
|
436
|
+
}, true, hook);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** A player was killed: Ham Sandwich's Killed after the game; the inflictor is the last that hurt him. */
|
|
440
|
+
export function playerKilledHlds(fire: Fire<PlayerKilledEvent>, hook: __Switch): void {
|
|
441
|
+
__ham(Ham_Killed, "player", (victim: number, killer: number, c: number, d: number): void => {
|
|
442
|
+
const inflictor = new Entity(victim).damageInflictor;
|
|
443
|
+
const event = new PlayerKilledEvent();
|
|
444
|
+
event.__give(0, <i32>victim);
|
|
445
|
+
event.__give(1, <i32>killer);
|
|
446
|
+
event.__give(2, <i32>inflictor);
|
|
447
|
+
hear(fire, event, "playerKilled");
|
|
448
|
+
}, true, hook);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** The game tells everyone of a death: its DeathMsg. */
|
|
452
|
+
export function deathNoticeHlds(fire: Fire<DeathNoticeEvent>, hook: __Switch): void {
|
|
453
|
+
onMessage("death", (message: DeathMsgMessage): void => {
|
|
454
|
+
const victim = message.victim;
|
|
455
|
+
const killer = message.killer;
|
|
456
|
+
const event = new DeathNoticeEvent();
|
|
457
|
+
event.__give(0, victim != null ? <i32>victim.id : 0);
|
|
458
|
+
event.__give(1, killer != null ? <i32>killer.id : 0);
|
|
459
|
+
hear(fire, event, "deathNotice");
|
|
460
|
+
}, hook);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** The death message itself: DeathMsg, which preventDefault() keeps from the players. */
|
|
464
|
+
export function sendDeathMessageHlds(fire: Fire<SendDeathMessageEvent>, hook: __Switch): void {
|
|
465
|
+
onMessage("death", (message: DeathMsgMessage): void => {
|
|
466
|
+
const victim = message.victim;
|
|
467
|
+
const killer = message.killer;
|
|
468
|
+
const event = new SendDeathMessageEvent();
|
|
469
|
+
event.__give(0, killer != null ? <i32>killer.id : 0);
|
|
470
|
+
event.__give(1, victim != null ? <i32>victim.id : 0);
|
|
471
|
+
event.__giveText(4, message.weapon);
|
|
472
|
+
// KillRarity's first flag is the headshot.
|
|
473
|
+
event.__give(6, message.headshot ? 1 : 0);
|
|
474
|
+
hear(fire, event, "sendDeathMessage", PLUGIN_HANDLED);
|
|
475
|
+
}, hook);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// A player's pain and death sounds - the game's CBasePlayer::Pain and
|
|
479
|
+
// DeathSound play nothing else, so stopping the sound stops the function.
|
|
480
|
+
const PAIN_SOUNDS: string[] = [
|
|
481
|
+
"player/bhit_flesh-1.wav", "player/bhit_flesh-2.wav", "player/bhit_flesh-3.wav",
|
|
482
|
+
"player/bhit_kevlar-1.wav", "player/bhit_helmet-1.wav",
|
|
483
|
+
"player/headshot1.wav", "player/headshot2.wav", "player/headshot3.wav"
|
|
484
|
+
];
|
|
485
|
+
const DEATH_SOUNDS: string[] = ["player/die1.wav", "player/die2.wav", "player/die3.wav", "player/death6.wav"];
|
|
486
|
+
|
|
487
|
+
/** A player's pain: the sound the game plays for it, his last hit group and his armour read from him. */
|
|
488
|
+
export function painHlds(fire: Fire<PainEvent>, hook: __Switch): void {
|
|
489
|
+
onSound((entity: i32, sample: string): void => {
|
|
490
|
+
if (playerOr0(entity) == 0 || !PAIN_SOUNDS.includes(sample)) return;
|
|
491
|
+
const hitGroup = <i32>get_ent_data(entity, "CBasePlayer", "m_LastHitGroup");
|
|
492
|
+
const armour = <i32>get_ent_data(entity, "CBasePlayer", "m_iKevlar") != 0;
|
|
493
|
+
const event = new PainEvent();
|
|
494
|
+
event.__give(0, entity);
|
|
495
|
+
event.__give(1, hitGroup);
|
|
496
|
+
event.__give(2, armour ? 1 : 0);
|
|
497
|
+
hear(fire, event, "pain", FMRES_SUPERCEDE);
|
|
498
|
+
}, hook);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** A player's death cry: the sound the game plays for it. */
|
|
502
|
+
export function deathSoundHlds(fire: Fire<DeathSoundEvent>, hook: __Switch): void {
|
|
503
|
+
onSound((entity: i32, sample: string): void => {
|
|
504
|
+
if (playerOr0(entity) == 0 || !DEATH_SOUNDS.includes(sample)) return;
|
|
505
|
+
const event = new DeathSoundEvent();
|
|
506
|
+
event.__give(0, entity);
|
|
507
|
+
hear(fire, event, "deathSound", FMRES_SUPERCEDE);
|
|
508
|
+
}, hook);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** A sound the game plays through the engine's EmitSound; the recipients are everyone. */
|
|
512
|
+
export function startSoundHlds(fire: Fire<StartSoundEvent>, hook: __Switch): void {
|
|
513
|
+
onSound((entity: i32, sample: string): void => {
|
|
514
|
+
const event = new StartSoundEvent();
|
|
515
|
+
event.__give(1, entity);
|
|
516
|
+
event.__give(2, <i32>arg(1));
|
|
517
|
+
event.__giveText(3, sample);
|
|
518
|
+
event.__give(4, <i32>Math.round(cellFloat(arg(3)) * 255));
|
|
519
|
+
event.__give(5, <i32>arg(4));
|
|
520
|
+
event.__give(6, <i32>arg(5));
|
|
521
|
+
event.__give(7, <i32>arg(6));
|
|
522
|
+
hear(fire, event, "startSound", FMRES_SUPERCEDE);
|
|
523
|
+
}, hook);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ---------------------------------------------------------------- the bomb and the VIP
|
|
527
|
+
|
|
528
|
+
/** The terrorist who gets the bomb at the round's start: the game logs "Spawned_With_The_Bomb". */
|
|
529
|
+
export function giveBombHlds(fire: Fire<GiveBombEvent>, hook: __Switch): void {
|
|
530
|
+
onPlayerLog("giveBomb", "Spawned_With_The_Bomb", (id: i32): void => {
|
|
531
|
+
const event = new GiveBombEvent();
|
|
532
|
+
event.__give(-1, id);
|
|
533
|
+
hear(fire, event, "giveBomb");
|
|
534
|
+
}, hook);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function becomeBomberHlds(fire: Fire<BecomeBomberEvent>, hook: __Switch): void {
|
|
538
|
+
onPlayerLog("becomeBomber", "Spawned_With_The_Bomb", (id: i32): void => {
|
|
539
|
+
const event = new BecomeBomberEvent();
|
|
540
|
+
event.__give(0, id);
|
|
541
|
+
event.__give(-1, 1);
|
|
542
|
+
hear(fire, event, "becomeBomber");
|
|
543
|
+
}, hook);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export function becomeVipHlds(fire: Fire<BecomeVipEvent>, hook: __Switch): void {
|
|
547
|
+
onPlayerLog("becomeVip", "Became_VIP", (id: i32): void => {
|
|
548
|
+
const event = new BecomeVipEvent();
|
|
549
|
+
event.__give(0, id);
|
|
550
|
+
hear(fire, event, "becomeVip");
|
|
551
|
+
}, hook);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** The bomb planted: the game gives the new bomb its model, with its planter, place and velocity set. */
|
|
555
|
+
export function plantBombHlds(fire: Fire<PlantBombEvent>, hook: __Switch): void {
|
|
556
|
+
onSetModel((entity: i32, model: string): void => {
|
|
557
|
+
if (model != "models/w_c4.mdl") return;
|
|
558
|
+
const bomb = new Entity(entity);
|
|
559
|
+
if (bomb.classname != "grenade") return;
|
|
560
|
+
const event = new PlantBombEvent();
|
|
561
|
+
event.__give(0, playerOr0(<i32>bomb.owner));
|
|
562
|
+
event.__giveVector(1, bomb.origin);
|
|
563
|
+
event.__giveVector(2, bomb.velocity);
|
|
564
|
+
event.__give(-1, entity);
|
|
565
|
+
hear(fire, event, "plantBomb");
|
|
566
|
+
}, hook);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** A defuse begins: the game logs "Begin_Bomb_Defuse_With_Kit", or "_Without_Kit". */
|
|
570
|
+
export function defuseBombStartHlds(fire: Fire<DefuseBombStartEvent>, hook: __Switch): void {
|
|
571
|
+
onLog("defuseBombStart", 3, "2&Begin_Bomb_Defuse_", (): void => {
|
|
572
|
+
const event = new DefuseBombStartEvent();
|
|
573
|
+
event.__give(0, plantedBomb());
|
|
574
|
+
event.__give(1, loggedPlayer());
|
|
575
|
+
hear(fire, event, "defuseBombStart");
|
|
576
|
+
}, hook);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** The bomb defused: the game logs "Defused_The_Bomb". */
|
|
580
|
+
export function defuseBombEndHlds(fire: Fire<DefuseBombEndEvent>, hook: __Switch): void {
|
|
581
|
+
onPlayerLog("defuseBombEnd", "Defused_The_Bomb", (id: i32): void => {
|
|
582
|
+
const event = new DefuseBombEndEvent();
|
|
583
|
+
event.__give(0, plantedBomb());
|
|
584
|
+
event.__give(1, id);
|
|
585
|
+
event.__give(2, 1);
|
|
586
|
+
hear(fire, event, "defuseBombEnd");
|
|
587
|
+
}, hook);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** The bomb blew up: the terrorists trigger "Target_Bombed". */
|
|
591
|
+
export function explodeBombHlds(fire: Fire<ExplodeBombEvent>, hook: __Switch): void {
|
|
592
|
+
onLog("explodeBomb", 6, "3=Target_Bombed", (): void => {
|
|
593
|
+
const event = new ExplodeBombEvent();
|
|
594
|
+
event.__give(0, plantedBomb());
|
|
595
|
+
hear(fire, event, "explodeBomb");
|
|
596
|
+
}, hook);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ---------------------------------------------------------------- money and buying
|
|
600
|
+
|
|
601
|
+
// The money the game last sent each player, by his index: the change is the
|
|
602
|
+
// next amount less this one. -1 for none yet.
|
|
603
|
+
const moneySent: i32[] = new Array<i32>(33).fill(-1);
|
|
604
|
+
|
|
605
|
+
/** A player's money changed: the Money message the game sends him, by how much it moved. */
|
|
606
|
+
export function addMoneyHlds(fire: Fire<AddMoneyEvent>, hook: __Switch): void {
|
|
607
|
+
// A bot is put in the server before the game has made him: his money is
|
|
608
|
+
// not there yet, and the first message he is sent is what counts from.
|
|
609
|
+
onServer<ClientPutinserverEvent>((event: ClientPutinserverEvent): void => {
|
|
610
|
+
const id = <i32>event.player.id;
|
|
611
|
+
moneySent[id] = pev_valid(id) == 2 ? <i32>cs_get_user_money(id) : -1;
|
|
612
|
+
}, hook);
|
|
613
|
+
// What everyone has, counted from now on - and again when the event is
|
|
614
|
+
// listened for after a time nothing was heard.
|
|
615
|
+
__whenUp(readMoney);
|
|
616
|
+
hook.add((on: bool): void => {
|
|
617
|
+
if (on) __whenUp(readMoney);
|
|
618
|
+
});
|
|
619
|
+
onMessage("money", (message: MoneyMessage): void => {
|
|
620
|
+
const player = message.player;
|
|
621
|
+
if (player == null) return;
|
|
622
|
+
const id = <i32>player.id;
|
|
623
|
+
const amount = <i32>message.amount;
|
|
624
|
+
const before = moneySent[id];
|
|
625
|
+
moneySent[id] = amount;
|
|
626
|
+
if (before < 0 || amount == before) return;
|
|
627
|
+
const event = new AddMoneyEvent();
|
|
628
|
+
event.__give(0, id);
|
|
629
|
+
event.__give(1, amount - before);
|
|
630
|
+
event.__give(3, message.flash ? 1 : 0);
|
|
631
|
+
hear(fire, event, "addMoney");
|
|
632
|
+
}, hook);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function readMoney(): void {
|
|
636
|
+
const max = get_maxplayers();
|
|
637
|
+
for (let id = 1; id <= max; id++) moneySent[id] = is_user_connected(id) != 0 ? <i32>cs_get_user_money(id) : -1;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/** What cstrike's CS_OnBuy hears, by the item it buys - each event takes its own. */
|
|
641
|
+
function onBuy(handler: (id: i32, item: i32) => void, hook: __Switch): void {
|
|
642
|
+
onServer<CS_OnBuyEvent>((event: CS_OnBuyEvent): void => handler(<i32>event.player.id, <i32>event.item), hook);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const EQUIPMENT: i32[] = [CSI_VEST, CSI_VESTHELM, CSI_FLASHBANG, CSI_HEGRENADE, CSI_SMOKEGRENADE, CSI_NVGS, CSI_DEFUSER, CSI_SHIELD];
|
|
646
|
+
|
|
647
|
+
/** A weapon bought: CS_OnBuy of a gun; a grenade is equipment. */
|
|
648
|
+
export function buyWeaponHlds(fire: Fire<BuyWeaponEvent>, hook: __Switch): void {
|
|
649
|
+
onBuy((id: i32, item: i32): void => {
|
|
650
|
+
if (item < 1 || item > CSI_LAST_WEAPON || EQUIPMENT.includes(item)) return;
|
|
651
|
+
const event = new BuyWeaponEvent();
|
|
652
|
+
event.__give(0, id);
|
|
653
|
+
event.__give(1, item);
|
|
654
|
+
hear(fire, event, "buyWeapon", PLUGIN_HANDLED);
|
|
655
|
+
}, hook);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** An item of the equipment menu bought: CS_OnBuy, its slot there. */
|
|
659
|
+
export function buyItemHlds(fire: Fire<BuyItemEvent>, hook: __Switch): void {
|
|
660
|
+
onBuy((id: i32, item: i32): void => {
|
|
661
|
+
const at = EQUIPMENT.indexOf(item == CSI_SHIELDGUN ? CSI_SHIELD : item);
|
|
662
|
+
if (at < 0) return;
|
|
663
|
+
const event = new BuyItemEvent();
|
|
664
|
+
event.__give(0, id);
|
|
665
|
+
event.__give(1, at + 1);
|
|
666
|
+
hear(fire, event, "buyItem", PLUGIN_HANDLED);
|
|
667
|
+
}, hook);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** Ammo bought: CS_OnBuy of a gun's ammo. */
|
|
671
|
+
export function buyAmmoHlds(fire: Fire<BuyAmmoEvent>, hook: __Switch): void {
|
|
672
|
+
onBuy((id: i32, item: i32): void => {
|
|
673
|
+
if (item != CSI_PRIAMMO && item != CSI_SECAMMO) return;
|
|
674
|
+
const event = new BuyAmmoEvent();
|
|
675
|
+
event.__give(0, id);
|
|
676
|
+
event.__give(2, 1);
|
|
677
|
+
event.__give(-1, 1);
|
|
678
|
+
hear(fire, event, "buyAmmo", PLUGIN_HANDLED);
|
|
679
|
+
}, hook);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** Whether an item is forbidden to buy: cstrike's CS_OnBuyAttempt, answered `true` to forbid. */
|
|
683
|
+
export function itemRestrictedHlds(fire: Fire<ItemRestrictedEvent>, hook: __Switch): void {
|
|
684
|
+
onServer<CS_OnBuyAttemptEvent>((attempt: CS_OnBuyAttemptEvent): void => {
|
|
685
|
+
const item = itemOf(<i32>attempt.item);
|
|
686
|
+
if (item < ITEM_SHIELDGUN) return;
|
|
687
|
+
const event = new ItemRestrictedEvent();
|
|
688
|
+
event.__give(0, <i32>attempt.player.id);
|
|
689
|
+
event.__give(1, item);
|
|
690
|
+
event.__give(2, ITEM_TYPE_BUYING);
|
|
691
|
+
fire(event, false);
|
|
692
|
+
fire(event, true);
|
|
693
|
+
if (event.__answered && event.__answerCell != 0) handled();
|
|
694
|
+
// Blocking it answers false: the game decides, as it does unasked.
|
|
695
|
+
event.__prevented = false;
|
|
696
|
+
settle(event, "itemRestricted", -1, true);
|
|
697
|
+
}, hook);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** A CSI_ item as ReGameDLL's ItemID: a weapon is the same number, equipment is not. */
|
|
701
|
+
function itemOf(csi: i32): i32 {
|
|
702
|
+
if (csi >= 1 && csi <= CSI_LAST_WEAPON) return csi;
|
|
703
|
+
if (csi == CSI_VEST) return ITEM_KEVLAR;
|
|
704
|
+
if (csi == CSI_VESTHELM) return ITEM_ASSAULT;
|
|
705
|
+
if (csi == CSI_DEFUSER) return ITEM_DEFUSEKIT;
|
|
706
|
+
if (csi == CSI_NVGS) return ITEM_NVG;
|
|
707
|
+
if (csi == CSI_SHIELD || csi == CSI_SHIELDGUN) return ITEM_SHIELDGUN;
|
|
708
|
+
return -1;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// ---------------------------------------------------------------- teams, menus, commands
|
|
712
|
+
|
|
713
|
+
/** A team picked: `jointeam`, or a number in the game's team menu. */
|
|
714
|
+
export function chooseTeamHlds(fire: Fire<ChooseTeamEvent>, hook: __Switch): void {
|
|
715
|
+
const chosen = (id: i32): void => {
|
|
716
|
+
const event = new ChooseTeamEvent();
|
|
717
|
+
event.__give(0, id);
|
|
718
|
+
event.__give(1, commandNumber());
|
|
719
|
+
hear(fire, event, "chooseTeam", PLUGIN_HANDLED);
|
|
720
|
+
};
|
|
721
|
+
onCommand("chooseTeam", "jointeam", chosen, hook);
|
|
722
|
+
onCommand("chooseTeam", "menuselect", (id: i32): void => {
|
|
723
|
+
const menu = new Player(id).openMenu;
|
|
724
|
+
if (menu == "team" || menu == "teamInGame") chosen(id);
|
|
725
|
+
}, hook);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** A model picked: `joinclass`, or a number in the game's class menu. */
|
|
729
|
+
export function chooseAppearanceHlds(fire: Fire<ChooseAppearanceEvent>, hook: __Switch): void {
|
|
730
|
+
const chosen = (id: i32): void => {
|
|
731
|
+
const event = new ChooseAppearanceEvent();
|
|
732
|
+
event.__give(0, id);
|
|
733
|
+
event.__give(1, commandNumber());
|
|
734
|
+
hear(fire, event, "chooseAppearance", PLUGIN_HANDLED);
|
|
735
|
+
};
|
|
736
|
+
onCommand("chooseAppearance", "joinclass", chosen, hook);
|
|
737
|
+
onCommand("chooseAppearance", "menuselect", (id: i32): void => {
|
|
738
|
+
if (new Player(id).openMenu == "appearance") chosen(id);
|
|
739
|
+
}, hook);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/** A weapon dropped: the player's `drop` command, with the weapon he names. */
|
|
743
|
+
export function dropPlayerItemHlds(fire: Fire<DropPlayerItemEvent>, hook: __Switch): void {
|
|
744
|
+
onCommand("dropPlayerItem", "drop", (id: i32): void => {
|
|
745
|
+
const event = new DropPlayerItemEvent();
|
|
746
|
+
event.__give(0, id);
|
|
747
|
+
event.__giveText(1, read_argv(1));
|
|
748
|
+
hear(fire, event, "dropPlayerItem", PLUGIN_HANDLED);
|
|
749
|
+
}, hook);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/** A VGUI menu shown: its VGUIMenu message, which preventDefault() keeps from the player. */
|
|
753
|
+
export function showVguiMenuHlds(fire: Fire<ShowVguiMenuEvent>, hook: __Switch): void {
|
|
754
|
+
onMessage("vguiMenu", (message: VGUIMenuMessage): void => {
|
|
755
|
+
const player = message.player;
|
|
756
|
+
const event = new ShowVguiMenuEvent();
|
|
757
|
+
event.__give(0, player != null ? <i32>player.id : 0);
|
|
758
|
+
event.__give(1, <i32>message.args.number(0));
|
|
759
|
+
event.__give(2, <i32>message.args.number(1));
|
|
760
|
+
hear(fire, event, "showVguiMenu", PLUGIN_HANDLED);
|
|
761
|
+
}, hook);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// ---------------------------------------------------------------- clients
|
|
765
|
+
|
|
766
|
+
export function clientConnectedHlds(fire: Fire<ClientConnectedEvent>, hook: __Switch): void {
|
|
767
|
+
onServer<ClientConnectEvent>((client: ClientConnectEvent): void => {
|
|
768
|
+
const event = new ClientConnectedEvent();
|
|
769
|
+
event.__give(0, <i32>client.player.id);
|
|
770
|
+
hear(fire, event, "clientConnected");
|
|
771
|
+
}, hook);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export function connectClientHlds(fire: Fire<ConnectClientEvent>, hook: __Switch): void {
|
|
775
|
+
onServer<ClientConnectEvent>((client: ClientConnectEvent): void => hear(fire, new ConnectClientEvent(), "connectClient"), hook);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/** A client dropped: AMX Mod X's client_disconnected, with the reason it was told. */
|
|
779
|
+
export function disconnectClientHlds(fire: Fire<DisconnectClientEvent>, hook: __Switch): void {
|
|
780
|
+
onServer<ClientDisconnectedEvent>((left: ClientDisconnectedEvent): void => {
|
|
781
|
+
const event = new DisconnectClientEvent();
|
|
782
|
+
event.__give(0, <i32>left.player.id);
|
|
783
|
+
event.__giveText(2, left.reason);
|
|
784
|
+
hear(fire, event, "disconnectClient");
|
|
785
|
+
}, hook);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
export function userInfoChangeHlds(fire: Fire<UserInfoChangeEvent>, hook: __Switch): void {
|
|
789
|
+
onServer<ClientInfochangedEvent>((changed: ClientInfochangedEvent): void => {
|
|
790
|
+
const event = new UserInfoChangeEvent();
|
|
791
|
+
event.__give(0, <i32>changed.player.id);
|
|
792
|
+
hear(fire, event, "userInfoChange");
|
|
793
|
+
}, hook);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/** A new name asked for: the info's name is not the one the player has. */
|
|
797
|
+
export function changeNameHlds(fire: Fire<ChangeNameEvent>, hook: __Switch): void {
|
|
798
|
+
onServer<ClientInfochangedEvent>((changed: ClientInfochangedEvent): void => {
|
|
799
|
+
const id = changed.player.id;
|
|
800
|
+
const wanted = get_user_info(id, "name");
|
|
801
|
+
if (wanted == get_user_name(id)) return;
|
|
802
|
+
const event = new ChangeNameEvent();
|
|
803
|
+
event.__give(0, <i32>id);
|
|
804
|
+
event.__giveText(2, wanted);
|
|
805
|
+
event.__give(-1, 1);
|
|
806
|
+
hear(fire, event, "changeName");
|
|
807
|
+
}, hook);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ---------------------------------------------------------------- items, effects, sounds
|
|
811
|
+
|
|
812
|
+
/** A player blinded: Ham Sandwich's CS_Player_Blind after the game - (untilTime, holdTime, fadeTime, alpha). */
|
|
813
|
+
export function playerBlindHlds(fire: Fire<PlayerBlindEvent>, hook: __Switch): void {
|
|
814
|
+
__ham(Ham_CS_Player_Blind, "player", (id: number, b: number, c: number, d: number): void => {
|
|
815
|
+
const event = new PlayerBlindEvent();
|
|
816
|
+
event.__give(0, <i32>id);
|
|
817
|
+
event.__give(3, <i32>arg(3));
|
|
818
|
+
event.__give(4, <i32>arg(2));
|
|
819
|
+
event.__give(5, <i32>arg(4));
|
|
820
|
+
hear(fire, event, "playerBlind");
|
|
821
|
+
}, true, hook);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Ham Sandwich's by-address answer.
|
|
825
|
+
const hamAnswer = new StaticArray<i32>(1);
|
|
826
|
+
|
|
827
|
+
/** A player got a weapon: Ham Sandwich's AddPlayerItem after the game, when it took the item. */
|
|
828
|
+
export function playerGotWeaponHlds(fire: Fire<PlayerGotWeaponEvent>, hook: __Switch): void {
|
|
829
|
+
__ham(Ham_AddPlayerItem, "player", (id: number, item: number, c: number, d: number): void => {
|
|
830
|
+
GetHamReturnInteger(changetype<i32>(hamAnswer));
|
|
831
|
+
if (hamAnswer[0] == 0) return;
|
|
832
|
+
const event = new PlayerGotWeaponEvent();
|
|
833
|
+
event.__give(0, <i32>id);
|
|
834
|
+
event.__give(1, <i32>item);
|
|
835
|
+
hear(fire, event, "playerGotWeapon");
|
|
836
|
+
}, true, hook);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Who hears whom: the game tells the engine for each pair of players, and the
|
|
841
|
+
* answer is told instead - preventDefault() answers `false`.
|
|
842
|
+
*/
|
|
843
|
+
export function canPlayerHearPlayerHlds(fire: Fire<CanPlayerHearPlayerEvent>, hook: __Switch): void {
|
|
844
|
+
onFakemeta(FM_Voice_SetClientListening, false, "voice", (): void => {
|
|
845
|
+
const listener = playerOr0(<i32>arg(0));
|
|
846
|
+
const sender = playerOr0(<i32>arg(1));
|
|
847
|
+
if (listener == 0 || sender == 0) return;
|
|
848
|
+
const event = new CanPlayerHearPlayerEvent();
|
|
849
|
+
event.__give(0, listener);
|
|
850
|
+
event.__give(1, sender);
|
|
851
|
+
event.__give(-1, <i32>arg(2));
|
|
852
|
+
fire(event, false);
|
|
853
|
+
fire(event, true);
|
|
854
|
+
if (event.__answered || event.__prevented) {
|
|
855
|
+
const hears = event.__prevented ? 0 : event.__answerCell;
|
|
856
|
+
new Call(NATIVE_engfunc).num(EngFunc_SetClientListening).ref(listener).ref(sender).ref(hears).run();
|
|
857
|
+
new Call(NATIVE_forward_return).num(FMV_CELL).ref(hears).run();
|
|
858
|
+
__outcome(FMRES_SUPERCEDE);
|
|
859
|
+
}
|
|
860
|
+
event.__prevented = false;
|
|
861
|
+
settle(event, "canPlayerHearPlayer", -1, true);
|
|
862
|
+
}, hook);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* A grenade thrown: the game gives it its model once its thrower, place,
|
|
867
|
+
* velocity and fuse are set - the time it blows up, less now.
|
|
868
|
+
*/
|
|
869
|
+
function onThrown(model: string, heard: (grenade: Entity, thrower: i32, fuse: f64) => void, hook: __Switch): void {
|
|
870
|
+
onSetModel((entity: i32, given: string): void => {
|
|
871
|
+
if (given != model) return;
|
|
872
|
+
const grenade = new Entity(entity);
|
|
873
|
+
if (grenade.classname != "grenade" || grenade.damageTime == 0) return;
|
|
874
|
+
heard(grenade, playerOr0(<i32>grenade.owner), grenade.damageTime - get_gametime());
|
|
875
|
+
}, hook);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
export function throwHeGrenadeHlds(fire: Fire<ThrowHeGrenadeEvent>, hook: __Switch): void {
|
|
879
|
+
onThrown("models/w_hegrenade.mdl", (grenade: Entity, thrower: i32, fuse: f64): void => {
|
|
880
|
+
const team = thrower > 0 ? <i32>get_ent_data(thrower, "CBasePlayer", "m_iTeam") : 0;
|
|
881
|
+
const event = new ThrowHeGrenadeEvent();
|
|
882
|
+
event.__give(0, thrower);
|
|
883
|
+
event.__giveVector(1, grenade.origin);
|
|
884
|
+
event.__giveVector(2, grenade.velocity);
|
|
885
|
+
event.__give(3, <i32>floatCell(fuse));
|
|
886
|
+
event.__give(4, team);
|
|
887
|
+
event.__give(-1, <i32>grenade.id);
|
|
888
|
+
hear(fire, event, "throwHeGrenade");
|
|
889
|
+
}, hook);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export function throwFlashbangHlds(fire: Fire<ThrowFlashbangEvent>, hook: __Switch): void {
|
|
893
|
+
onThrown("models/w_flashbang.mdl", (grenade: Entity, thrower: i32, fuse: f64): void => {
|
|
894
|
+
const event = new ThrowFlashbangEvent();
|
|
895
|
+
event.__give(0, thrower);
|
|
896
|
+
event.__giveVector(1, grenade.origin);
|
|
897
|
+
event.__giveVector(2, grenade.velocity);
|
|
898
|
+
event.__give(3, <i32>floatCell(fuse));
|
|
899
|
+
event.__give(-1, <i32>grenade.id);
|
|
900
|
+
hear(fire, event, "throwFlashbang");
|
|
901
|
+
}, hook);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
export function throwSmokeGrenadeHlds(fire: Fire<ThrowSmokeGrenadeEvent>, hook: __Switch): void {
|
|
905
|
+
onThrown("models/w_smokegrenade.mdl", (grenade: Entity, thrower: i32, fuse: f64): void => {
|
|
906
|
+
const event = new ThrowSmokeGrenadeEvent();
|
|
907
|
+
event.__give(0, thrower);
|
|
908
|
+
event.__giveVector(1, grenade.origin);
|
|
909
|
+
event.__giveVector(2, grenade.velocity);
|
|
910
|
+
event.__give(3, <i32>floatCell(fuse));
|
|
911
|
+
event.__give(-1, <i32>grenade.id);
|
|
912
|
+
hear(fire, event, "throwSmokeGrenade");
|
|
913
|
+
}, hook);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const THROWN: string[] = ["models/w_hegrenade.mdl", "models/w_flashbang.mdl", "models/w_smokegrenade.mdl"];
|
|
917
|
+
|
|
918
|
+
/** Any grenade thrown; the grenade's weapon is the one in the thrower's hands. */
|
|
919
|
+
export function throwGrenadeHlds(fire: Fire<ThrowGrenadeEvent>, hook: __Switch): void {
|
|
920
|
+
for (let i = 0; i < THROWN.length; i++) {
|
|
921
|
+
onThrown(THROWN[i], (grenade: Entity, thrower: i32, fuse: f64): void => {
|
|
922
|
+
const weapon = thrower > 0 ? new Player(thrower).activeItem : null;
|
|
923
|
+
const event = new ThrowGrenadeEvent();
|
|
924
|
+
event.__give(0, thrower);
|
|
925
|
+
event.__give(1, weapon != null ? <i32>weapon.id : 0);
|
|
926
|
+
event.__giveVector(2, grenade.origin);
|
|
927
|
+
event.__giveVector(3, grenade.velocity);
|
|
928
|
+
event.__give(4, <i32>floatCell(fuse));
|
|
929
|
+
event.__give(-1, <i32>grenade.id);
|
|
930
|
+
hear(fire, event, "throwGrenade");
|
|
931
|
+
}, hook);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/** A weaponbox given its model: preventDefault() keeps it off. */
|
|
936
|
+
export function setModelHlds(fire: Fire<SetModelEvent>, hook: __Switch): void {
|
|
937
|
+
onSetModel((entity: i32, model: string): void => {
|
|
938
|
+
if (new Entity(entity).classname != "weaponbox") return;
|
|
939
|
+
const event = new SetModelEvent();
|
|
940
|
+
event.__give(0, entity);
|
|
941
|
+
event.__giveText(1, model);
|
|
942
|
+
hear(fire, event, "setModel", FMRES_SUPERCEDE);
|
|
943
|
+
}, hook);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/** A gib spawned: the game gives it its model last. */
|
|
947
|
+
export function gibSpawnHlds(fire: Fire<GibSpawnEvent>, hook: __Switch): void {
|
|
948
|
+
onSetModel((entity: i32, model: string): void => {
|
|
949
|
+
if (new Entity(entity).classname != "gib") return;
|
|
950
|
+
const event = new GibSpawnEvent();
|
|
951
|
+
event.__give(0, entity);
|
|
952
|
+
event.__giveText(1, model);
|
|
953
|
+
hear(fire, event, "gibSpawn");
|
|
954
|
+
}, hook);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/** A gib touched something: the engine module's touch of the gib, which preventDefault() blocks. */
|
|
958
|
+
export function bounceGibTouchHlds(fire: Fire<BounceGibTouchEvent>, hook: __Switch): void {
|
|
959
|
+
const touched = (touch: TouchEvent): void => {
|
|
960
|
+
const event = new BounceGibTouchEvent();
|
|
961
|
+
event.__give(0, <i32>touch.toucher.id);
|
|
962
|
+
event.__give(1, <i32>touch.touched.id);
|
|
963
|
+
hear(fire, event, "bounceGibTouch", PLUGIN_HANDLED);
|
|
964
|
+
};
|
|
965
|
+
game.addEventListener("touch", touched, { toucher: "gib" });
|
|
966
|
+
hook.add((on: bool): void => {
|
|
967
|
+
if (on) game.addEventListener("touch", touched, { toucher: "gib" });
|
|
968
|
+
else game.removeEventListener("touch", touched, { toucher: "gib" });
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* The game precaches a file: fakemeta's forward, added at plugin_precache,
|
|
974
|
+
* before the game's own. The listeners before the game hear it as it is
|
|
975
|
+
* asked, preventDefault() skips it; the ones after hear the engine's answer.
|
|
976
|
+
*/
|
|
977
|
+
function onPrecache<E>(fn: i32, key: string, make: () => E, fire: Fire<E>, name: string, hook: __Switch): void {
|
|
978
|
+
onFakemeta(fn, false, `${key}:pre`, (): void => {
|
|
979
|
+
const event = make();
|
|
980
|
+
const fields = changetype<HookEvent>(event);
|
|
981
|
+
fields.__giveText(0, argText(0));
|
|
982
|
+
fire(event, false);
|
|
983
|
+
settle(changetype<HookEvent>(event), name, FMRES_SUPERCEDE);
|
|
984
|
+
}, hook, true);
|
|
985
|
+
onFakemeta(fn, true, `${key}:post`, (): void => {
|
|
986
|
+
const event = make();
|
|
987
|
+
const fields = changetype<HookEvent>(event);
|
|
988
|
+
fields.__giveText(0, argText(0));
|
|
989
|
+
fields.__give(-1, <i32>get_orig_retval());
|
|
990
|
+
fire(event, true);
|
|
991
|
+
settle(changetype<HookEvent>(event), name);
|
|
992
|
+
}, hook, true);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
export function precacheModelHlds(fire: Fire<PrecacheModelEvent>, hook: __Switch): void {
|
|
996
|
+
onPrecache<PrecacheModelEvent>(FM_PrecacheModel, "model", (): PrecacheModelEvent => new PrecacheModelEvent(), fire, "precacheModel", hook);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
export function precacheSoundHlds(fire: Fire<PrecacheSoundEvent>, hook: __Switch): void {
|
|
1000
|
+
onPrecache<PrecacheSoundEvent>(FM_PrecacheSound, "sound", (): PrecacheSoundEvent => new PrecacheSoundEvent(), fire, "precacheSound", hook);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
export function precacheFileHlds(fire: Fire<PrecacheFileEvent>, hook: __Switch): void {
|
|
1004
|
+
onPrecache<PrecacheFileEvent>(FM_PrecacheGeneric, "generic", (): PrecacheFileEvent => new PrecacheFileEvent(), fire, "precacheFile", hook);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// ---------------------------------------------------------------- the game rules' fields
|
|
1008
|
+
|
|
1009
|
+
// The game's name a plugin wrote, answered for the game's own; "" for none yet.
|
|
1010
|
+
let gameNameWritten = "";
|
|
1011
|
+
|
|
1012
|
+
/** The game's name in the server browser: the one written, or the game's own. */
|
|
1013
|
+
export function gameNameHlds(): string {
|
|
1014
|
+
if (gameNameWritten.length > 0) return gameNameWritten;
|
|
1015
|
+
const text = new CellBuffer(64);
|
|
1016
|
+
new Call(NATIVE_dllfunc).num(DLLFunc_GetGameDescription).tailBuffer(text, 63).run();
|
|
1017
|
+
return text.text();
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/** Writes the game's name: the game's GetGameDescription is answered with it from now on. */
|
|
1021
|
+
export function setGameNameHlds(value: string): void {
|
|
1022
|
+
if (gameNameWritten.length == 0) {
|
|
1023
|
+
onFakemeta(FM_GetGameDescription, false, "gamedesc", (): void => {
|
|
1024
|
+
new Call(NATIVE_forward_return).num(FMV_STRING).str(gameNameWritten).run();
|
|
1025
|
+
__outcome(FMRES_SUPERCEDE);
|
|
1026
|
+
}, null);
|
|
1027
|
+
}
|
|
1028
|
+
gameNameWritten = value;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* The game time the game began: AMX Mod X counts the map's time left from
|
|
1033
|
+
* the map's start, or from the last restart it saw announced; with no time
|
|
1034
|
+
* limit there is none, and it is 0.
|
|
1035
|
+
*/
|
|
1036
|
+
function gameStart(): f64 {
|
|
1037
|
+
const limit = get_cvar_float("mp_timelimit") * 60;
|
|
1038
|
+
return limit > 0 ? get_gametime() + get_timeleft() - limit : 0;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
export function gameStartTimeHlds(): i32 {
|
|
1042
|
+
return <i32>floatCell(gameStart());
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
export function setGameStartTimeHlds(cell: i32): void {
|
|
1046
|
+
readOnly("gameStartTime");
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/** The game time the map ends by its time limit, 0 for none: mp_timelimit after the game's start. */
|
|
1050
|
+
export function timeLimitHlds(): i32 {
|
|
1051
|
+
const limit = get_cvar_float("mp_timelimit") * 60;
|
|
1052
|
+
return <i32>floatCell(limit > 0 ? gameStart() + limit : 0);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/** Moves the map's end: mp_timelimit, in minutes after the game's start. */
|
|
1056
|
+
export function setTimeLimitHlds(cell: i32): void {
|
|
1057
|
+
const end = cellFloat(cell);
|
|
1058
|
+
set_cvar_float("mp_timelimit", end > 0 ? max((end - gameStart()) / 60, 0) : 0);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
export function maxPlayersHlds(): i32 {
|
|
1062
|
+
return <i32>get_maxplayers();
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
export function setMaxPlayersHlds(cell: i32): void {
|
|
1066
|
+
readOnly("maxPlayers");
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function readOnly(name: string): void {
|
|
1070
|
+
__sayOnce(`game.${name} is read from the server without ReAPI here, and writing it does nothing`);
|
|
1071
|
+
}
|