@sagl/server 0.0.0-stage → 0.1.0-next.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -2
  3. package/dist/actions.d.ts +46 -0
  4. package/dist/actions.js +80 -0
  5. package/dist/actions.js.map +1 -0
  6. package/dist/animations.d.ts +32 -0
  7. package/dist/animations.js +95 -0
  8. package/dist/animations.js.map +1 -0
  9. package/dist/audio.d.ts +180 -0
  10. package/dist/audio.js +353 -0
  11. package/dist/audio.js.map +1 -0
  12. package/dist/audio_effects.d.ts +219 -0
  13. package/dist/audio_effects.js +84 -0
  14. package/dist/audio_effects.js.map +1 -0
  15. package/dist/camera.d.ts +188 -0
  16. package/dist/camera.js +247 -0
  17. package/dist/camera.js.map +1 -0
  18. package/dist/claims.d.ts +27 -0
  19. package/dist/claims.js +48 -0
  20. package/dist/claims.js.map +1 -0
  21. package/dist/commands.d.ts +135 -0
  22. package/dist/commands.js +373 -0
  23. package/dist/commands.js.map +1 -0
  24. package/dist/console.d.ts +21 -0
  25. package/dist/console.js +100 -0
  26. package/dist/console.js.map +1 -0
  27. package/dist/constants.d.ts +654 -0
  28. package/dist/constants.js +657 -0
  29. package/dist/constants.js.map +1 -0
  30. package/dist/cutscene.d.ts +47 -0
  31. package/dist/cutscene.js +190 -0
  32. package/dist/cutscene.js.map +1 -0
  33. package/dist/draw.d.ts +106 -0
  34. package/dist/draw.js +132 -0
  35. package/dist/draw.js.map +1 -0
  36. package/dist/effects.d.ts +91 -0
  37. package/dist/effects.js +116 -0
  38. package/dist/effects.js.map +1 -0
  39. package/dist/gangzones.d.ts +47 -0
  40. package/dist/gangzones.js +122 -0
  41. package/dist/gangzones.js.map +1 -0
  42. package/dist/geo.d.ts +11 -0
  43. package/dist/geo.js +25 -0
  44. package/dist/geo.js.map +1 -0
  45. package/dist/hud.d.ts +159 -0
  46. package/dist/hud.js +247 -0
  47. package/dist/hud.js.map +1 -0
  48. package/dist/index.d.ts +40 -0
  49. package/dist/index.js +43 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/interaction.d.ts +81 -0
  52. package/dist/interaction.js +143 -0
  53. package/dist/interaction.js.map +1 -0
  54. package/dist/junctions.d.ts +1 -0
  55. package/dist/junctions.js +330 -0
  56. package/dist/junctions.js.map +1 -0
  57. package/dist/map.d.ts +55 -0
  58. package/dist/map.js +71 -0
  59. package/dist/map.js.map +1 -0
  60. package/dist/menu.d.ts +96 -0
  61. package/dist/menu.js +129 -0
  62. package/dist/menu.js.map +1 -0
  63. package/dist/mission.d.ts +482 -0
  64. package/dist/mission.js +1258 -0
  65. package/dist/mission.js.map +1 -0
  66. package/dist/models.d.ts +56 -0
  67. package/dist/models.js +71 -0
  68. package/dist/models.js.map +1 -0
  69. package/dist/modshop.d.ts +229 -0
  70. package/dist/modshop.js +904 -0
  71. package/dist/modshop.js.map +1 -0
  72. package/dist/modshop_data.d.ts +44 -0
  73. package/dist/modshop_data.js +287 -0
  74. package/dist/modshop_data.js.map +1 -0
  75. package/dist/native.d.ts +809 -0
  76. package/dist/native.js +27 -0
  77. package/dist/native.js.map +1 -0
  78. package/dist/objects.d.ts +249 -0
  79. package/dist/objects.js +455 -0
  80. package/dist/objects.js.map +1 -0
  81. package/dist/paint.d.ts +124 -0
  82. package/dist/paint.js +84 -0
  83. package/dist/paint.js.map +1 -0
  84. package/dist/paynspray.d.ts +44 -0
  85. package/dist/paynspray.js +177 -0
  86. package/dist/paynspray.js.map +1 -0
  87. package/dist/ped.d.ts +335 -0
  88. package/dist/ped.js +536 -0
  89. package/dist/ped.js.map +1 -0
  90. package/dist/player.d.ts +685 -0
  91. package/dist/player.js +1163 -0
  92. package/dist/player.js.map +1 -0
  93. package/dist/screeneffects.d.ts +99 -0
  94. package/dist/screeneffects.js +257 -0
  95. package/dist/screeneffects.js.map +1 -0
  96. package/dist/scripts.d.ts +69 -0
  97. package/dist/scripts.js +125 -0
  98. package/dist/scripts.js.map +1 -0
  99. package/dist/server.d.ts +713 -0
  100. package/dist/server.js +1080 -0
  101. package/dist/server.js.map +1 -0
  102. package/dist/shopblips.d.ts +9 -0
  103. package/dist/shopblips.js +30 -0
  104. package/dist/shopblips.js.map +1 -0
  105. package/dist/special.d.ts +33 -0
  106. package/dist/special.js +26 -0
  107. package/dist/special.js.map +1 -0
  108. package/dist/splash.d.ts +42 -0
  109. package/dist/splash.js +55 -0
  110. package/dist/splash.js.map +1 -0
  111. package/dist/text.d.ts +19 -0
  112. package/dist/text.js +25 -0
  113. package/dist/text.js.map +1 -0
  114. package/dist/ui.d.ts +141 -0
  115. package/dist/ui.js +243 -0
  116. package/dist/ui.js.map +1 -0
  117. package/dist/vehicle.d.ts +306 -0
  118. package/dist/vehicle.js +612 -0
  119. package/dist/vehicle.js.map +1 -0
  120. package/dist/vehiclecolors.d.ts +26 -0
  121. package/dist/vehiclecolors.js +49 -0
  122. package/dist/vehiclecolors.js.map +1 -0
  123. package/dist/world.d.ts +222 -0
  124. package/dist/world.js +227 -0
  125. package/dist/world.js.map +1 -0
  126. package/dist/zone.d.ts +89 -0
  127. package/dist/zone.js +140 -0
  128. package/dist/zone.js.map +1 -0
  129. package/package.json +33 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Burnett-Taylor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,62 @@
1
- # Temporary Holding Version
1
+ # @sagl/server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The server for **SAGL**, a multiplayer mod for GTA: San Andreas, built for
4
+ roleplay servers. Write your gamemode in TypeScript or JavaScript on
5
+ Node.js: players, vehicles, peds, missions, UI, cameras, shaders and more,
6
+ with the server in control.
7
+
8
+ ```sh
9
+ npm install @sagl/server
10
+ ```
11
+
12
+ ```ts
13
+ import { Server, Color } from "@sagl/server";
14
+
15
+ const server = new Server({ name: "My server", port: 7777 });
16
+
17
+ server.on("playerJoin", (player) => {
18
+ player.send(`Welcome, **${player.name}**! Press {key:chat} to talk.`, Color.White, { markdown: true });
19
+ });
20
+
21
+ server.on("playerSpawn", (player) => {
22
+ player.setPosition({ x: 2495, y: -1670, z: 13.4 }, 90);
23
+ });
24
+
25
+ server.listen();
26
+ ```
27
+
28
+ Run it with `node index.ts`: Node 22.18 and newer run TypeScript directly
29
+ (older 22s need `--experimental-strip-types`). Players join with the SAGL
30
+ client.
31
+
32
+ ## Documentation
33
+
34
+ **[docs.sagl.mp](https://docs.sagl.mp)**: getting started, the full API,
35
+ and the example gamemodes.
36
+
37
+ ## Platforms
38
+
39
+ The server's core is native code, installed for your computer
40
+ automatically:
41
+
42
+ | Platform | Package |
43
+ |---|---|
44
+ | Linux x64 (glibc 2.28 or newer: Debian 10+, Ubuntu 20.04+, RHEL 8+) | `@sagl/server-linux-x64` |
45
+ | Windows x64 | `@sagl/server-win32-x64` |
46
+
47
+ Node.js 22 or newer.
48
+
49
+ ## Versions
50
+
51
+ SAGL's client, server and this SDK share one version. Within the same
52
+ **minor** version (0.4.x) clients and servers work together; a new minor
53
+ version changes the network protocol, so update servers and players'
54
+ clients together. `Server.version` and `Server.protocolVersion` say what
55
+ you're running.
56
+
57
+ `npm install @sagl/server` gets the latest release; `@sagl/server@next` the
58
+ newest prerelease, built from every change.
59
+
60
+ ## Licence
61
+
62
+ MIT
@@ -0,0 +1,46 @@
1
+ import type { NativeServer } from "./native.js";
2
+ import type { Player } from "./player.js";
3
+ /**
4
+ * Key names for actions' default keys, by their place on a US keyboard
5
+ * (players see what their own layout prints there). Escape and Alt can't
6
+ * be bound.
7
+ */
8
+ export declare const KEY_NAMES: readonly string[];
9
+ export interface ActionOptions {
10
+ /** What players see in Settings, Controls. */
11
+ label: string;
12
+ /** Its key until a player binds their own (`KEY_NAMES`); none if left out. */
13
+ key?: string;
14
+ }
15
+ /**
16
+ * The server's actions (`server.actions`): things players do with a key
17
+ * of their choosing. Each shows in their Settings, Controls, under the
18
+ * server's name, with its default key; they can bind it to another (or
19
+ * two), kept for this server. Pressing it reaches `playerAction` (and the
20
+ * handler given here). Client scripts have the same: `sagl.actions`, which
21
+ * also hears the server's.
22
+ *
23
+ * Keys work while the game has the keyboard (not while typing in the chat
24
+ * or a menu), and the game still sees them: pick keys the game doesn't use
25
+ * on foot or driving (or let players choose).
26
+ *
27
+ * ```ts
28
+ * server.actions.register("engine", { label: "Engine on/off", key: "N" }, (player) => {
29
+ * const v = player.vehicle;
30
+ * if (v && player.seat === 0) v.engine = !v.engine;
31
+ * });
32
+ * ```
33
+ */
34
+ export declare class Actions {
35
+ #private;
36
+ /** @internal */
37
+ constructor(native: NativeServer);
38
+ /** Defines (or redefines) an action: `id` up to 32 characters. */
39
+ register(id: string, options: ActionOptions, handler?: (player: Player) => void): void;
40
+ /** Removes an action (from players' Controls too). */
41
+ remove(id: string): void;
42
+ /** The actions defined. */
43
+ get ids(): string[];
44
+ /** @internal A player pressed one. */
45
+ pressed(player: Player, id: string): void;
46
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Key names for actions' default keys, by their place on a US keyboard
3
+ * (players see what their own layout prints there). Escape and Alt can't
4
+ * be bound.
5
+ */
6
+ export const KEY_NAMES = [
7
+ ..."ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""),
8
+ ...Array.from({ length: 24 }, (_, i) => `F${i + 1}`),
9
+ ...Array.from({ length: 10 }, (_, i) => `Numpad${i}`),
10
+ "NumpadMultiply", "NumpadPlus", "NumpadMinus", "NumpadDecimal", "NumpadDivide",
11
+ "Space", "Tab", "Enter", "Backspace", "Insert", "Delete", "Home", "End", "PageUp", "PageDown",
12
+ "Up", "Down", "Left", "Right", "Shift", "Ctrl", "CapsLock",
13
+ "Comma", "Period", "Semicolon", "Slash", "Minus", "Equals", "LeftBracket", "RightBracket",
14
+ "Backslash", "Quote", "Backquote", "IntlBackslash",
15
+ ];
16
+ /**
17
+ * The server's actions (`server.actions`): things players do with a key
18
+ * of their choosing. Each shows in their Settings, Controls, under the
19
+ * server's name, with its default key; they can bind it to another (or
20
+ * two), kept for this server. Pressing it reaches `playerAction` (and the
21
+ * handler given here). Client scripts have the same: `sagl.actions`, which
22
+ * also hears the server's.
23
+ *
24
+ * Keys work while the game has the keyboard (not while typing in the chat
25
+ * or a menu), and the game still sees them: pick keys the game doesn't use
26
+ * on foot or driving (or let players choose).
27
+ *
28
+ * ```ts
29
+ * server.actions.register("engine", { label: "Engine on/off", key: "N" }, (player) => {
30
+ * const v = player.vehicle;
31
+ * if (v && player.seat === 0) v.engine = !v.engine;
32
+ * });
33
+ * ```
34
+ */
35
+ export class Actions {
36
+ #native;
37
+ #handlers = new Map();
38
+ #defined = new Set();
39
+ /** @internal */
40
+ constructor(native) {
41
+ this.#native = native;
42
+ }
43
+ /** Defines (or redefines) an action: `id` up to 32 characters. */
44
+ register(id, options, handler) {
45
+ if (!/^[A-Za-z0-9_.:-]{1,32}$/.test(id))
46
+ throw new RangeError(`action id "${id}": 1-32 of A-Z, 0-9, _ . : -`);
47
+ const key = options.key ?? "";
48
+ if (key && !KEY_NAMES.includes(key))
49
+ throw new RangeError(`not a key name: "${key}" (see KEY_NAMES)`);
50
+ this.#native.defineAction(id, String(options.label).slice(0, 64), key);
51
+ this.#defined.add(id);
52
+ if (handler)
53
+ this.#handlers.set(id, handler);
54
+ else
55
+ this.#handlers.delete(id);
56
+ }
57
+ /** Removes an action (from players' Controls too). */
58
+ remove(id) {
59
+ this.#native.removeAction(id);
60
+ this.#defined.delete(id);
61
+ this.#handlers.delete(id);
62
+ }
63
+ /** The actions defined. */
64
+ get ids() {
65
+ return [...this.#defined];
66
+ }
67
+ /** @internal A player pressed one. */
68
+ pressed(player, id) {
69
+ const handler = this.#handlers.get(id);
70
+ if (!handler)
71
+ return;
72
+ try {
73
+ handler(player);
74
+ }
75
+ catch (err) {
76
+ console.error(`[sagl] action "${id}" failed:`, err);
77
+ }
78
+ }
79
+ }
80
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { NativeServer } from "./native.js";
2
+ import type { Player } from "./player.js";
3
+ /** One of the server's own animation sets: an IFP client file. */
4
+ export interface AnimationSetInfo {
5
+ /** The set's name (inside the file): what `playAnim(set, name)` takes. */
6
+ name: string;
7
+ /** The client file. */
8
+ file: string;
9
+ /** How many animations it has. */
10
+ animations: number;
11
+ }
12
+ /**
13
+ * The server's own animations: every `.ifp` among the client files (San
14
+ * Andreas' format, or III / Vice City's older one) is an animation set,
15
+ * named by the name inside it. `player.playAnim(set, name)` and
16
+ * `ped.playAnim(set, name)` play them like the game's own; players' games
17
+ * load a set when it's first played, and unload idle ones when they need
18
+ * room. `server.animations`.
19
+ */
20
+ export declare class ServerAnimations {
21
+ #private;
22
+ /** @internal */
23
+ constructor(native: NativeServer, players: () => Player[], dir: string | undefined);
24
+ /** The sets, by name. */
25
+ get sets(): AnimationSetInfo[];
26
+ /** Whether there's a set of that name (the server's own, not the game's). */
27
+ has(name: string): boolean;
28
+ /** @internal Reads the client files' IFPs again (with `server.rescanClientFiles()`). */
29
+ rescan(): void;
30
+ /** @internal A player joined: every set. */
31
+ replay(player: Player): void;
32
+ }
@@ -0,0 +1,95 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /**
4
+ * The server's own animations: every `.ifp` among the client files (San
5
+ * Andreas' format, or III / Vice City's older one) is an animation set,
6
+ * named by the name inside it. `player.playAnim(set, name)` and
7
+ * `ped.playAnim(set, name)` play them like the game's own; players' games
8
+ * load a set when it's first played, and unload idle ones when they need
9
+ * room. `server.animations`.
10
+ */
11
+ export class ServerAnimations {
12
+ #native;
13
+ #players;
14
+ #dir;
15
+ #sets = new Map();
16
+ /** @internal */
17
+ constructor(native, players, dir) {
18
+ this.#native = native;
19
+ this.#players = players;
20
+ this.#dir = dir;
21
+ this.rescan();
22
+ }
23
+ /** The sets, by name. */
24
+ get sets() {
25
+ return [...this.#sets.values()];
26
+ }
27
+ /** Whether there's a set of that name (the server's own, not the game's). */
28
+ has(name) {
29
+ return this.#sets.has(name.toUpperCase());
30
+ }
31
+ /** @internal Reads the client files' IFPs again (with `server.rescanClientFiles()`). */
32
+ rescan() {
33
+ const old = this.#sets;
34
+ this.#sets = new Map();
35
+ if (this.#dir)
36
+ this.#scan(this.#dir, "");
37
+ for (const p of this.#players()) {
38
+ for (const [key, set] of old)
39
+ if (!this.#sets.has(key))
40
+ this.#native.animationSet(p.id, set.name, "");
41
+ for (const set of this.#sets.values())
42
+ this.#native.animationSet(p.id, set.name, set.file);
43
+ }
44
+ }
45
+ /** @internal A player joined: every set. */
46
+ replay(player) {
47
+ for (const set of this.#sets.values())
48
+ this.#native.animationSet(player.id, set.name, set.file);
49
+ }
50
+ #scan(dir, prefix) {
51
+ for (const entry of fs.readdirSync(path.join(dir, prefix), { withFileTypes: true })) {
52
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
53
+ if (entry.isDirectory()) {
54
+ this.#scan(dir, rel);
55
+ }
56
+ else if (entry.name.toLowerCase().endsWith(".ifp")) {
57
+ const info = readHeader(path.join(dir, rel));
58
+ if (!info) {
59
+ console.warn(`[sagl] animations: ${rel} isn't an animation file (IFP)`);
60
+ }
61
+ else if (info.name.length > 15 || !/^[A-Za-z0-9_]+$/.test(info.name)) {
62
+ console.warn(`[sagl] animations: ${rel}'s set name "${info.name}" must be 1-15 letters, digits or _`);
63
+ }
64
+ else if (this.#sets.has(info.name.toUpperCase())) {
65
+ console.warn(`[sagl] animations: ${rel} and ${this.#sets.get(info.name.toUpperCase()).file} are both set "${info.name}"`);
66
+ }
67
+ else {
68
+ this.#sets.set(info.name.toUpperCase(), { name: info.name, file: rel, animations: info.animations });
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ // The set name and animation count from an IFP's header.
75
+ function readHeader(file) {
76
+ const fd = fs.openSync(file, "r");
77
+ try {
78
+ const b = Buffer.alloc(64);
79
+ fs.readSync(fd, b, 0, 64, 0);
80
+ const tag = b.toString("latin1", 0, 4);
81
+ const cstr = (at, max) => {
82
+ const end = b.indexOf(0, at);
83
+ return b.toString("latin1", at, end < 0 || end > at + max ? at + max : end);
84
+ };
85
+ if (tag === "ANP3" || tag === "ANP2")
86
+ return { name: cstr(8, 24), animations: b.readUInt32LE(32) };
87
+ if (tag === "ANPK" && b.toString("latin1", 8, 12) === "INFO")
88
+ return { name: cstr(20, 24), animations: b.readUInt32LE(16) };
89
+ return null;
90
+ }
91
+ finally {
92
+ fs.closeSync(fd);
93
+ }
94
+ }
95
+ //# sourceMappingURL=animations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animations.js","sourceRoot":"","sources":["../src/animations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ import type { NativeServer } from "./native.js";
2
+ import type { Ped } from "./ped.js";
3
+ import { Player, type Vector3 } from "./player.js";
4
+ import { Vehicle } from "./vehicle.js";
5
+ import type { Zone } from "./zone.js";
6
+ import { type AudioEffects } from "./audio_effects.js";
7
+ export interface SoundOptions {
8
+ /** Heard from this spot, or from this ped, player or vehicle (following it). Left out: everywhere, as is. */
9
+ at?: Vector3 | Ped | Player | Vehicle;
10
+ /** How far it carries (m) when it has a place. Default 30. */
11
+ range?: number;
12
+ /** Plays until `stop()`. */
13
+ loop?: boolean;
14
+ /** 0-1, of each player's sound effects volume. Default 1. */
15
+ volume?: number;
16
+ /** Who hears it. Default everyone (players joining later too, while it loops). */
17
+ to?: readonly Player[];
18
+ /** Through audio effects: a preset (`"phone"`, `"hall"`...) or stages layered. See `AudioEffects`. */
19
+ effects?: AudioEffects;
20
+ /**
21
+ * Positioned: its highs fade with distance, and it's muffled with a
22
+ * building between it and the listener. Default `server.audio.realism`.
23
+ */
24
+ realism?: boolean;
25
+ /**
26
+ * Speech (dialogue, a phone call): the radio goes quiet while it plays
27
+ * and it's near enough to hear, as for the game's mission dialogue.
28
+ */
29
+ speech?: boolean;
30
+ }
31
+ /** A sound playing (`server.audio.play()`). */
32
+ export declare class Sound {
33
+ #private;
34
+ readonly id: number;
35
+ readonly file: string;
36
+ /** How long the file plays (ms), read from the server's copy; undefined if it can't be read. */
37
+ readonly duration: number | undefined;
38
+ /**
39
+ * Resolves when it's over: when it has played through (by its duration,
40
+ * from when the server sent it), or is stopped. A looping one, when
41
+ * stopped.
42
+ *
43
+ * ```ts
44
+ * await server.audio.play("audio/caller.mp3", { to: [player] }).done;
45
+ * ```
46
+ */
47
+ readonly done: Promise<void>;
48
+ /** @internal */
49
+ constructor(id: number, file: string, duration: number | undefined, loop: boolean, stop: (s: Sound) => void);
50
+ /** False once it has played through or been stopped. */
51
+ get playing(): boolean;
52
+ stop(): void;
53
+ }
54
+ /**
55
+ * A line as subtitles: its sentences, short ones together, up to about
56
+ * `max` characters each (default 70).
57
+ */
58
+ export declare function splitSubtitles(text: string, max?: number): string[];
59
+ export interface SpeakOptions extends Omit<SoundOptions, "loop"> {
60
+ /**
61
+ * What's said, shown as subtitles in step with the recording: a string
62
+ * (split into sentence-sized subtitles), or the subtitles one by one.
63
+ * Each shows for its share of the recording, by length.
64
+ */
65
+ subtitle?: string | readonly string[];
66
+ /** Who sees the subtitles. Default: `to`, else everyone within `range` of `at`, else everyone. */
67
+ subtitleTo?: readonly Player[];
68
+ /** Before each subtitle: a colour code such as `"~y~"`. */
69
+ prefix?: string;
70
+ /** Through the glass of `at` (a vehicle): muffled, as someone shut inside it. */
71
+ muffled?: boolean;
72
+ }
73
+ /** A line being spoken (`server.audio.speak()`). */
74
+ export interface Speech {
75
+ readonly sound: Sound;
76
+ /** Resolves when it's been said (or stopped). */
77
+ readonly done: Promise<void>;
78
+ /** Stops it and its subtitles. */
79
+ stop(): void;
80
+ }
81
+ /**
82
+ * Sounds from client files (MP3, WAV, FLAC) on players' games:
83
+ * `server.audio`. Positioned ones are heard from where they are, fading
84
+ * with distance, as the game's own sounds.
85
+ */
86
+ export declare class ServerAudio {
87
+ #private;
88
+ /**
89
+ * The default for positioned sounds' `realism` (highs fading with
90
+ * distance, muffled behind buildings). Default false.
91
+ */
92
+ realism: boolean;
93
+ /** @internal */
94
+ constructor(native: NativeServer, players: () => Player[], clientFiles?: string);
95
+ /**
96
+ * How long a client file plays (ms), from the server's copy (the
97
+ * `clientFiles` directory); undefined if it isn't there or isn't a sound.
98
+ */
99
+ duration(file: string): number | undefined;
100
+ /**
101
+ * Plays a recorded line with its subtitles timed to it, as speech (the
102
+ * radio ducks under it). `at` a ped or vehicle: from them (`muffled`:
103
+ * from inside the vehicle, through the glass).
104
+ *
105
+ * ```ts
106
+ * await server.audio.speak("audio/benny-my-car.mp3", { at: benny, subtitle: "My car! That's my car!" }).done;
107
+ * await server.audio.speak("audio/caller.mp3", { to: [player], effects: "phone", subtitle: lines, prefix: "~y~" }).done;
108
+ * ```
109
+ */
110
+ speak(file: string, options?: SpeakOptions): Speech;
111
+ /**
112
+ * Plays a client file. `at` places it (a spot, or a ped, player or
113
+ * vehicle it follows); `to` picks who hears it.
114
+ *
115
+ * ```ts
116
+ * const ring = server.audio.play("audio/payphone-ring.mp3", { at: phone, loop: true, range: 25 });
117
+ * ring.stop();
118
+ * ```
119
+ */
120
+ play(file: string, options?: SoundOptions): Sound;
121
+ /**
122
+ * Plays one of the game's own sounds (see `GameSound`): a frontend one
123
+ * (the HUD's: pickups, the Pay 'n' Spray jingle), or, with `at`, one of
124
+ * the mission script's sounds at a spot. `to` picks who hears it
125
+ * (default everyone).
126
+ *
127
+ * ```ts
128
+ * server.audio.playGame(GameSound.CarRespray, { to: [player] });
129
+ * ```
130
+ */
131
+ playGame(sound: number, options?: {
132
+ at?: Vector3;
133
+ to?: readonly Player[];
134
+ }): void;
135
+ /**
136
+ * Plays one of the game's mission audio, by its audio id: the recordings
137
+ * and effects its missions and machines load (a vending machine's clunk:
138
+ * 42600). From a player, ped or vehicle (following them), a spot, or
139
+ * (no `at`) as is. `to` picks who hears it (default everyone). Each
140
+ * game reads it from its own copy and plays it as the server's sounds,
141
+ * so `effects` work on it.
142
+ *
143
+ * ```ts
144
+ * server.audio.playMission(42600, { at: player });
145
+ * ```
146
+ */
147
+ playMission(id: number, options?: {
148
+ at?: Vector3 | Player | Ped | Vehicle;
149
+ range?: number;
150
+ to?: readonly Player[];
151
+ effects?: AudioEffects;
152
+ realism?: boolean;
153
+ }): void;
154
+ /**
155
+ * The effects every sound of the server's and its scripts' passes through
156
+ * for this player now: where they are (a hall's reverb, a tunnel's echo).
157
+ * `null`: none. The game's own world sounds aren't affected.
158
+ *
159
+ * ```ts
160
+ * server.audio.setEnvironment(player, "tunnel");
161
+ * ```
162
+ */
163
+ setEnvironment(player: Player, effects: AudioEffects | null): void;
164
+ /**
165
+ * An environment for everyone inside a zone, while they're in it (the
166
+ * most recently entered, where zones overlap; one set with
167
+ * `setEnvironment` wins over all).
168
+ *
169
+ * ```ts
170
+ * server.audio.setZoneEnvironment(server.createZone({ ... }), "hall");
171
+ * ```
172
+ */
173
+ setZoneEnvironment(zone: Zone, effects: AudioEffects | null): void;
174
+ /** @internal */
175
+ zoneEvent(player: Player, zone: Zone, entered: boolean): void;
176
+ /** @internal */
177
+ forget(player: Player): void;
178
+ /** @internal A player joined: the loops playing for everyone. */
179
+ replay(player: Player): void;
180
+ }