@juande_pl/rocketleague-stats-api-client 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +204 -177
- package/dist/{chunk-IVUQE3LJ.js → chunk-5ZC7WJPK.js} +51 -1
- package/dist/chunk-5ZC7WJPK.js.map +1 -0
- package/dist/index.cjs +58 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +15 -3
- package/dist/node.cjs +56 -0
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +14 -2
- package/package.json +1 -1
- package/dist/chunk-IVUQE3LJ.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,177 +1,204 @@
|
|
|
1
|
-
# rocketleague-stats-api-client
|
|
2
|
-
|
|
3
|
-
Fully typed WebSocket client for the official [Rocket League Stats API](https://www.rocketleague.com/developer/stats-api), with runtime type guards for every event and command.
|
|
4
|
-
|
|
5
|
-
- Typed interfaces for all 22 events and 6 commands
|
|
6
|
-
- Type guards that validate each message at runtime (enabled by default)
|
|
7
|
-
- `RocketLeagueStatsClient`: connects automatically, reconnects with backoff, and gives you typed `on()` / `onAny()` / `send()`
|
|
8
|
-
- No runtime dependencies. Works in Node.js >= 22 and in browsers. ESM and CommonJS builds.
|
|
9
|
-
|
|
10
|
-
## Install
|
|
11
|
-
|
|
12
|
-
```sh
|
|
13
|
-
npm install @juande_pl/rocketleague-stats-api-client
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Enable the Stats API in Rocket League
|
|
17
|
-
|
|
18
|
-
Edit `<Install Dir>\TAGame\Config\TAStatsAPI.ini` (or `DefaultStatsAPI.ini` if it doesn't exist) **before launching the game**:
|
|
19
|
-
|
|
20
|
-
```ini
|
|
21
|
-
[TAGame.MatchStatsExporter_TA]
|
|
22
|
-
PacketSendRate=30
|
|
23
|
-
WebPort=49124
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
| Setting | Default | Description |
|
|
27
|
-
| ---------------- | ------- | ------------------------------------------------------------------- |
|
|
28
|
-
| `PacketSendRate` | `0` | `UpdateState` packets per second (max 120). `0` disables the feature. |
|
|
29
|
-
| `Port` | `49123` | TCP port. `0` disables it. |
|
|
30
|
-
| `WebPort` | `49124` | WebSocket port used by this library. `0` disables it. |
|
|
31
|
-
|
|
32
|
-
### Automatic setup (Node.js)
|
|
33
|
-
|
|
34
|
-
On Node.js the client checks the `.ini` for you. It looks for `TAStatsAPI.ini` first, then `DefaultStatsAPI.ini`. On Windows it finds the Epic Games and Steam installs by itself. If `PacketSendRate` is `0` or less, missing, or above the maximum of `120`, it sets it to `30` and warns that **you must restart the game** for the Stats API to work. The game only reads this file at launch. In browsers the check is skipped and the Node-only code is never loaded.
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import { RocketLeagueStatsClient } from '@juande_pl/rocketleague-stats-api-client';
|
|
38
|
-
|
|
39
|
-
const client = new RocketLeagueStatsClient({
|
|
40
|
-
ensureStatsApi: true, // default. Or false, or { installDir, iniPath, packetSendRate, warn }
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const config = await client.statsApiConfig;
|
|
44
|
-
// { status: 'updated', iniPath, previous: 0, packetSendRate: 30, restartRequired: true }
|
|
45
|
-
// or 'enabled' | 'not-found' | 'write-failed' (e.g. no permission under Program Files)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
The helpers are exported from `@juande_pl/rocketleague-stats-api-client/node`. Run the check on its own with `ensureStatsApiEnabled(options)`, or use `editPacketSendRate(content)` to edit the `.ini` text without touching the disk.
|
|
49
|
-
|
|
50
|
-
## Usage
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
import { RocketLeagueStatsClient } from "@juande_pl/rocketleague-stats-api-client";
|
|
54
|
-
|
|
55
|
-
const client = new RocketLeagueStatsClient(); // ws://127.0.0.1:49124, connects automatically
|
|
56
|
-
|
|
57
|
-
// A single event: `data` is typed as GoalScoredData
|
|
58
|
-
client.on('GoalScored', (data) => {
|
|
59
|
-
console.log(`${data.Scorer.Name} scored at ${data.GoalSpeed} uu/s`);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Every event, as a discriminated union
|
|
63
|
-
client.onAny((event) => {
|
|
64
|
-
if (event.Event === 'StatfeedEvent') {
|
|
65
|
-
console.log(event.Data.MainTarget.Name, event.Data.EventName);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Commands: checked at compile time and validated at runtime
|
|
70
|
-
client.send('ChangePOV', { Focus: '1', Perspective: 'PlayerView' });
|
|
71
|
-
client.send({ Command: 'SetHUDVisibility', Data: { bVisible: false } });
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Options
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
new RocketLeagueStatsClient({
|
|
78
|
-
host: '127.0.0.1', // default
|
|
79
|
-
port: 49124, // default (WebPort)
|
|
80
|
-
secure: false, // use wss://
|
|
81
|
-
url: undefined, // full URL, overrides host/port/secure
|
|
82
|
-
autoConnect: true, // set to false and call client.connect() yourself
|
|
83
|
-
reconnect: true, // or false, or { initialDelay, maxDelay, factor, maxAttempts }
|
|
84
|
-
validateEvents: true, // run type guards on incoming events
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
|
|
135
|
-
`
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
1
|
+
# rocketleague-stats-api-client
|
|
2
|
+
|
|
3
|
+
Fully typed WebSocket client for the official [Rocket League Stats API](https://www.rocketleague.com/developer/stats-api), with runtime type guards for every event and command.
|
|
4
|
+
|
|
5
|
+
- Typed interfaces for all 22 events and 6 commands
|
|
6
|
+
- Type guards that validate each message at runtime (enabled by default)
|
|
7
|
+
- `RocketLeagueStatsClient`: connects automatically, reconnects with backoff, and gives you typed `on()` / `onAny()` / `send()`
|
|
8
|
+
- No runtime dependencies. Works in Node.js >= 22 and in browsers. ESM and CommonJS builds.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @juande_pl/rocketleague-stats-api-client
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Enable the Stats API in Rocket League
|
|
17
|
+
|
|
18
|
+
Edit `<Install Dir>\TAGame\Config\TAStatsAPI.ini` (or `DefaultStatsAPI.ini` if it doesn't exist) **before launching the game**:
|
|
19
|
+
|
|
20
|
+
```ini
|
|
21
|
+
[TAGame.MatchStatsExporter_TA]
|
|
22
|
+
PacketSendRate=30
|
|
23
|
+
WebPort=49124
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Setting | Default | Description |
|
|
27
|
+
| ---------------- | ------- | ------------------------------------------------------------------- |
|
|
28
|
+
| `PacketSendRate` | `0` | `UpdateState` packets per second (max 120). `0` disables the feature. |
|
|
29
|
+
| `Port` | `49123` | TCP port. `0` disables it. |
|
|
30
|
+
| `WebPort` | `49124` | WebSocket port used by this library. `0` disables it. |
|
|
31
|
+
|
|
32
|
+
### Automatic setup (Node.js)
|
|
33
|
+
|
|
34
|
+
On Node.js the client checks the `.ini` for you. It looks for `TAStatsAPI.ini` first, then `DefaultStatsAPI.ini`. On Windows it finds the Epic Games and Steam installs by itself. If `PacketSendRate` is `0` or less, missing, or above the maximum of `120`, it sets it to `30` and warns that **you must restart the game** for the Stats API to work. The game only reads this file at launch. In browsers the check is skipped and the Node-only code is never loaded.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { RocketLeagueStatsClient } from '@juande_pl/rocketleague-stats-api-client';
|
|
38
|
+
|
|
39
|
+
const client = new RocketLeagueStatsClient({
|
|
40
|
+
ensureStatsApi: true, // default. Or false, or { installDir, iniPath, packetSendRate, warn }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const config = await client.statsApiConfig;
|
|
44
|
+
// { status: 'updated', iniPath, previous: 0, packetSendRate: 30, restartRequired: true }
|
|
45
|
+
// or 'enabled' | 'not-found' | 'write-failed' (e.g. no permission under Program Files)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The helpers are exported from `@juande_pl/rocketleague-stats-api-client/node`. Run the check on its own with `ensureStatsApiEnabled(options)`, or use `editPacketSendRate(content)` to edit the `.ini` text without touching the disk.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { RocketLeagueStatsClient } from "@juande_pl/rocketleague-stats-api-client";
|
|
54
|
+
|
|
55
|
+
const client = new RocketLeagueStatsClient(); // ws://127.0.0.1:49124, connects automatically
|
|
56
|
+
|
|
57
|
+
// A single event: `data` is typed as GoalScoredData
|
|
58
|
+
client.on('GoalScored', (data) => {
|
|
59
|
+
console.log(`${data.Scorer.Name} scored at ${data.GoalSpeed} uu/s`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Every event, as a discriminated union
|
|
63
|
+
client.onAny((event) => {
|
|
64
|
+
if (event.Event === 'StatfeedEvent') {
|
|
65
|
+
console.log(event.Data.MainTarget.Name, event.Data.EventName);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Commands: checked at compile time and validated at runtime
|
|
70
|
+
client.send('ChangePOV', { Focus: '1', Perspective: 'PlayerView' });
|
|
71
|
+
client.send({ Command: 'SetHUDVisibility', Data: { bVisible: false } });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Options
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
new RocketLeagueStatsClient({
|
|
78
|
+
host: '127.0.0.1', // default
|
|
79
|
+
port: 49124, // default (WebPort)
|
|
80
|
+
secure: false, // use wss://
|
|
81
|
+
url: undefined, // full URL, overrides host/port/secure
|
|
82
|
+
autoConnect: true, // set to false and call client.connect() yourself
|
|
83
|
+
reconnect: true, // or false, or { initialDelay, maxDelay, factor, maxAttempts }
|
|
84
|
+
validateEvents: true, // run type guards on incoming events
|
|
85
|
+
ignoreReplayEndGoal: true, // drop the fake GoalScored sent when a replay ends
|
|
86
|
+
sortPlayers: undefined, // sort UpdateState.Players (see below). Default: the game's order
|
|
87
|
+
WebSocket: undefined, // custom implementation, defaults to globalThis.WebSocket
|
|
88
|
+
ensureStatsApi: true, // Node.js only: enable PacketSendRate in the .ini (see above)
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
With `autoConnect: true`, the connection opens on the next microtask, so listeners you add right after `new` still get the `connect` event. If the game isn't running yet, the client keeps retrying with exponential backoff (1 s, 2 s, 4 s … up to 30 s).
|
|
93
|
+
|
|
94
|
+
On Node.js < 22, pass the `ws` package: `new RocketLeagueStatsClient({ WebSocket: (await import('ws')).default })`.
|
|
95
|
+
|
|
96
|
+
### Listening
|
|
97
|
+
|
|
98
|
+
| Method | Description |
|
|
99
|
+
| ------------------------------- | --------------------------------------------------------------------------- |
|
|
100
|
+
| `on(name, listener)` | Game event (`'GoalScored'`, …) or lifecycle event. Returns an unsubscribe function. |
|
|
101
|
+
| `once(name, listener)` | Same as `on`, but the listener runs only once. |
|
|
102
|
+
| `off(name, listener)` | Removes a listener. |
|
|
103
|
+
| `onAny(listener)` / `offAny()` | Every game event, as `StatsApiEvent` (narrow with `event.Event`). |
|
|
104
|
+
| `waitFor(name, { timeout })` | Promise that resolves with the next payload of that event. |
|
|
105
|
+
| `removeAllListeners(name?)` | Removes every listener, or every listener for one name. |
|
|
106
|
+
|
|
107
|
+
Game event listeners receive `(data, event)`: the typed payload and the full `{ Event, Data }` message.
|
|
108
|
+
|
|
109
|
+
Lifecycle events:
|
|
110
|
+
|
|
111
|
+
| Name | Arguments |
|
|
112
|
+
| ---------------- | ------------------------------------------- |
|
|
113
|
+
| `connect` | none |
|
|
114
|
+
| `disconnect` | `{ code, reason, willReconnect }` |
|
|
115
|
+
| `reconnecting` | `{ attempt, delay }` |
|
|
116
|
+
| `error` | `Error` (`ConnectionError`, `ListenerError`) |
|
|
117
|
+
| `invalidMessage` | `ParseFailure` (`reason`, `raw`, `event?`, `data?`) |
|
|
118
|
+
|
|
119
|
+
Errors are only reported to `error` listeners. A throwing listener doesn't stop the other listeners from running.
|
|
120
|
+
|
|
121
|
+
### Sending commands
|
|
122
|
+
|
|
123
|
+
`send(command, data)` or `send({ Command, Data })`. It throws:
|
|
124
|
+
|
|
125
|
+
- `CommandValidationError` if the command name or payload is invalid (for example, `ChangePOV` with neither `Focus` nor `Perspective`, or `SetGameSpeed` with a negative speed).
|
|
126
|
+
- `NotConnectedError` if the socket is not open.
|
|
127
|
+
|
|
128
|
+
| Command | Payload |
|
|
129
|
+
| ------------------ | --------------------------------------------------------------- |
|
|
130
|
+
| `ChangePOV` | `{ Focus?: 'Ball' \| '<shortcut>', Perspective?: Perspective }` (at least one) |
|
|
131
|
+
| `LoadReplay` | `{ FileName?: string, Path?: string }` (at least one) |
|
|
132
|
+
| `SeekReplay` | `{ Frame?: number, TimeSeconds?: number }` (at least one) |
|
|
133
|
+
| `SetGameSpeed` | `{ Speed: number }` (≥ 0) |
|
|
134
|
+
| `SetHUDVisibility` | `{ bVisible: boolean }` |
|
|
135
|
+
| `SetMatchPaused` | `{ bPaused: boolean }` |
|
|
136
|
+
|
|
137
|
+
`Perspective` is one of `'Fly' | 'SoftAttach' | 'HardAttach' | 'PlayerView' | 'AutoCam' | 'Camera_Director'`.
|
|
138
|
+
|
|
139
|
+
## Sorting players
|
|
140
|
+
|
|
141
|
+
By default `UpdateState.Players` arrives in the order the game sends it. To change that, pass `sortPlayers` to the client, or call `sortPlayers()` yourself on any player list:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { RocketLeagueStatsClient, sortPlayers, byTeam, byStat, byName, byShortcut } from '@juande_pl/rocketleague-stats-api-client';
|
|
145
|
+
|
|
146
|
+
// Every UpdateState: Blue then Orange, best score first within each team
|
|
147
|
+
const client = new RocketLeagueStatsClient({ sortPlayers: [byTeam(), byStat('Score')] });
|
|
148
|
+
|
|
149
|
+
// Or on demand. Returns a new array and doesn't change the original.
|
|
150
|
+
const byBoost = sortPlayers(data.Players, byStat('Boost'));
|
|
151
|
+
const custom = sortPlayers(data.Players, (a, b) => a.Demos - b.Demos);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
| Comparator | Order |
|
|
155
|
+
| ----------------------------- | ------------------------------------------------------------------ |
|
|
156
|
+
| `byTeam(order = 'asc')` | Blue (0) first. `'desc'` puts Orange first. |
|
|
157
|
+
| `byShortcut(order = 'asc')` | Spectator shortcut. |
|
|
158
|
+
| `byName(order = 'asc')` | Name, case-insensitive. |
|
|
159
|
+
| `byStat(stat, order = 'desc')`| Any numeric field (`Score`, `Goals`, `Boost`…). Missing values go last. |
|
|
160
|
+
|
|
161
|
+
The first comparator decides and the next ones break ties. The sort is stable, so players that compare equal keep the game's order.
|
|
162
|
+
|
|
163
|
+
## Type guards
|
|
164
|
+
|
|
165
|
+
Every guard is exported, so you can validate messages from any source (the TCP port, recordings, a relay server…):
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { isStatsApiEvent, isGoalScoredData, isEventData, isStatsApiCommand, parseEventMessage } from '@juande_pl/rocketleague-stats-api-client';
|
|
169
|
+
|
|
170
|
+
isGoalScoredData(payload); // payload is GoalScoredData
|
|
171
|
+
isEventData('BallHit', payload); // payload is BallHitData
|
|
172
|
+
isStatsApiEvent(message); // message is StatsApiEvent
|
|
173
|
+
isStatsApiEvent(message, 'MatchEnded'); // message is StatsApiEventOf<'MatchEnded'>
|
|
174
|
+
isStatsApiCommand(command, 'ChangePOV'); // command is StatsApiCommandOf<'ChangePOV'>
|
|
175
|
+
|
|
176
|
+
// Decodes the wire format ({ Event, Data: "<json string>" }). Never throws.
|
|
177
|
+
const result = parseEventMessage(rawText);
|
|
178
|
+
if (result.ok) console.log(result.event.Event);
|
|
179
|
+
else console.warn(result.reason); // 'invalid-json' | 'invalid-envelope' | 'invalid-data-json' | 'unknown-event' | 'invalid-data'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Guards accept extra, undocumented fields, so new fields added by a game update don't break validation. Fields the docs mark `SPECTATOR` or `CONDITIONAL` (and `MatchGuid`, which is only set online or on LAN) are optional.
|
|
183
|
+
|
|
184
|
+
The building blocks (`shape`, `optional`, `arrayOf`, `oneOf`, `isString`, `isNumber`, …) are exported too.
|
|
185
|
+
|
|
186
|
+
## Known quirks
|
|
187
|
+
|
|
188
|
+
- `StatfeedEvent.Type` is a localized label. Use `EventName` in your logic.
|
|
189
|
+
- `GoalScored.GoalTime` is the length of the previous round, not the match clock.
|
|
190
|
+
- In offline exhibitions or with splitscreen players, `StatfeedEvent` can arrive more than once.
|
|
191
|
+
- When a replay ends, the game sends a fake `GoalScored` event whose `Scorer` is `{ "Name": "", "Shortcut": 0, "TeamNum": 0 }`. The client drops it by default. Pass `ignoreReplayEndGoal: false` to receive it, and use `isReplayEndGoal(event)` to detect it yourself.
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
npm install
|
|
197
|
+
npm run typecheck
|
|
198
|
+
npm test
|
|
199
|
+
npm run build
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT. This project is not affiliated with Psyonix or Epic Games.
|
|
@@ -333,6 +333,37 @@ function parseEventMessage(raw, options = {}) {
|
|
|
333
333
|
return { ok: true, event: { Event: event, Data: data } };
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
// src/sort.ts
|
|
337
|
+
var direction = (order) => order === "asc" ? 1 : -1;
|
|
338
|
+
function byTeam(order = "asc") {
|
|
339
|
+
return (a, b) => (a.TeamNum - b.TeamNum) * direction(order);
|
|
340
|
+
}
|
|
341
|
+
function byShortcut(order = "asc") {
|
|
342
|
+
return (a, b) => (a.Shortcut - b.Shortcut) * direction(order);
|
|
343
|
+
}
|
|
344
|
+
function byName(order = "asc") {
|
|
345
|
+
return (a, b) => a.Name.localeCompare(b.Name, void 0, { sensitivity: "base" }) * direction(order);
|
|
346
|
+
}
|
|
347
|
+
function byStat(stat, order = "desc") {
|
|
348
|
+
return (a, b) => {
|
|
349
|
+
const x = a[stat];
|
|
350
|
+
const y = b[stat];
|
|
351
|
+
if (x === void 0 || y === void 0) return x === y ? 0 : x === void 0 ? 1 : -1;
|
|
352
|
+
return (x - y) * direction(order);
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function sortPlayers(players, ...comparators) {
|
|
356
|
+
const copy = [...players];
|
|
357
|
+
if (comparators.length === 0) return copy;
|
|
358
|
+
return copy.sort((a, b) => {
|
|
359
|
+
for (const compare of comparators) {
|
|
360
|
+
const result = compare(a, b);
|
|
361
|
+
if (result !== 0) return result;
|
|
362
|
+
}
|
|
363
|
+
return 0;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
336
367
|
// src/errors.ts
|
|
337
368
|
var StatsApiError = class extends Error {
|
|
338
369
|
name = "StatsApiError";
|
|
@@ -367,6 +398,11 @@ var ListenerError = class extends StatsApiError {
|
|
|
367
398
|
|
|
368
399
|
// src/client.ts
|
|
369
400
|
var WS_OPEN = 1;
|
|
401
|
+
function isReplayEndGoal(event) {
|
|
402
|
+
if (event.Event !== "GoalScored") return false;
|
|
403
|
+
const { Name, Shortcut, TeamNum } = event.Data.Scorer;
|
|
404
|
+
return Name === "" && Shortcut === 0 && TeamNum === 0;
|
|
405
|
+
}
|
|
370
406
|
function isNodeRuntime() {
|
|
371
407
|
const proc = globalThis.process;
|
|
372
408
|
return typeof proc?.versions?.node === "string";
|
|
@@ -382,6 +418,8 @@ var RocketLeagueStatsClient = class {
|
|
|
382
418
|
#autoConnect;
|
|
383
419
|
#reconnect;
|
|
384
420
|
#validateEvents;
|
|
421
|
+
#ignoreReplayEndGoal;
|
|
422
|
+
#playerComparators;
|
|
385
423
|
#WebSocket;
|
|
386
424
|
#socket = null;
|
|
387
425
|
#state = "idle";
|
|
@@ -395,6 +433,8 @@ var RocketLeagueStatsClient = class {
|
|
|
395
433
|
this.url = options.url ?? `${protocol}://${options.host ?? "127.0.0.1"}:${options.port ?? 49124}`;
|
|
396
434
|
this.#autoConnect = options.autoConnect ?? true;
|
|
397
435
|
this.#validateEvents = options.validateEvents ?? true;
|
|
436
|
+
this.#ignoreReplayEndGoal = options.ignoreReplayEndGoal ?? true;
|
|
437
|
+
this.#playerComparators = options.sortPlayers === void 0 ? [] : [options.sortPlayers].flat();
|
|
398
438
|
this.#WebSocket = options.WebSocket ?? globalThis.WebSocket;
|
|
399
439
|
const reconnect = options.reconnect ?? true;
|
|
400
440
|
this.#reconnect = reconnect === false ? null : {
|
|
@@ -533,6 +573,10 @@ var RocketLeagueStatsClient = class {
|
|
|
533
573
|
return;
|
|
534
574
|
}
|
|
535
575
|
const { event } = result;
|
|
576
|
+
if (this.#ignoreReplayEndGoal && isReplayEndGoal(event)) return;
|
|
577
|
+
if (this.#playerComparators.length > 0 && event.Event === "UpdateState" && Array.isArray(event.Data.Players)) {
|
|
578
|
+
event.Data.Players = sortPlayers(event.Data.Players, ...this.#playerComparators);
|
|
579
|
+
}
|
|
536
580
|
this.#dispatch(event.Event, [event.Data, event]);
|
|
537
581
|
for (const listener of [...this.#anyListeners]) {
|
|
538
582
|
this.#invoke("*", listener, [event]);
|
|
@@ -716,11 +760,17 @@ export {
|
|
|
716
760
|
isCommandData,
|
|
717
761
|
isStatsApiCommand,
|
|
718
762
|
parseEventMessage,
|
|
763
|
+
byTeam,
|
|
764
|
+
byShortcut,
|
|
765
|
+
byName,
|
|
766
|
+
byStat,
|
|
767
|
+
sortPlayers,
|
|
719
768
|
StatsApiError,
|
|
720
769
|
NotConnectedError,
|
|
721
770
|
CommandValidationError,
|
|
722
771
|
ConnectionError,
|
|
723
772
|
ListenerError,
|
|
773
|
+
isReplayEndGoal,
|
|
724
774
|
RocketLeagueStatsClient
|
|
725
775
|
};
|
|
726
|
-
//# sourceMappingURL=chunk-
|
|
776
|
+
//# sourceMappingURL=chunk-5ZC7WJPK.js.map
|