@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/dist/index.d.ts CHANGED
@@ -596,6 +596,33 @@ interface ParseOptions {
596
596
  */
597
597
  declare function parseEventMessage(raw: unknown, options?: ParseOptions): ParseResult;
598
598
 
599
+ /** Compares two players like an `Array.prototype.sort` callback. */
600
+ type PlayerComparator<T extends PlayerRef = Player> = (a: T, b: T) => number;
601
+ /** Sort direction. */
602
+ type SortOrder = 'asc' | 'desc';
603
+ /** Numeric `Player` fields that can be used with {@link byStat}. */
604
+ type PlayerStat = {
605
+ [K in keyof Player]-?: NonNullable<Player[K]> extends number ? K : never;
606
+ }[keyof Player];
607
+ /** Orders by team: Blue (0) first, or Orange (1) first with `'desc'`. */
608
+ declare function byTeam<T extends PlayerRef>(order?: SortOrder): PlayerComparator<T>;
609
+ /** Orders by spectator shortcut. */
610
+ declare function byShortcut<T extends PlayerRef>(order?: SortOrder): PlayerComparator<T>;
611
+ /** Orders by display name (case-insensitive, locale-aware). */
612
+ declare function byName<T extends PlayerRef>(order?: SortOrder): PlayerComparator<T>;
613
+ /** Orders by a numeric stat (`Score`, `Goals`, `Boost`…). Highest first by default. Missing values go last. */
614
+ declare function byStat(stat: PlayerStat, order?: SortOrder): PlayerComparator;
615
+ /**
616
+ * Returns a new array sorted by the given comparators: the first one decides, the next ones break ties.
617
+ * The sort is stable, so players that compare equal keep their original order. The input is not modified.
618
+ *
619
+ * ```ts
620
+ * sortPlayers(data.Players, byTeam(), byStat('Score')); // Blue then Orange, best score first
621
+ * sortPlayers(data.Players, (a, b) => a.Demos - b.Demos); // any custom comparator
622
+ * ```
623
+ */
624
+ declare function sortPlayers<T extends PlayerRef>(players: readonly T[], ...comparators: PlayerComparator<T>[]): T[];
625
+
599
626
  /** Base class for every error thrown or emitted by this library. */
600
627
  declare class StatsApiError extends Error {
601
628
  name: string;
@@ -773,6 +800,17 @@ interface StatsApiClientOptions {
773
800
  * @default true
774
801
  */
775
802
  validateEvents?: boolean;
803
+ /**
804
+ * Drop the fake `GoalScored` event the game sends when a replay ends
805
+ * (its `Scorer` is `{ Name: '', Shortcut: 0, TeamNum: 0 }`). See {@link isReplayEndGoal}.
806
+ * @default true
807
+ */
808
+ ignoreReplayEndGoal?: boolean;
809
+ /**
810
+ * Sort `UpdateState.Players` before listeners receive it. Pass one comparator, or several to break ties
811
+ * (e.g. `[byTeam(), byStat('Score')]`). When omitted, players keep the order sent by the game.
812
+ */
813
+ sortPlayers?: PlayerComparator | PlayerComparator[];
776
814
  /**
777
815
  * WebSocket implementation. Defaults to `globalThis.WebSocket`
778
816
  * (browsers, Node.js >= 22). Pass `WebSocket` from the `ws` package for older runtimes.
@@ -786,6 +824,11 @@ interface StatsApiClientOptions {
786
824
  */
787
825
  ensureStatsApi?: boolean | EnsureStatsApiOptions;
788
826
  }
827
+ /**
828
+ * `true` for the fake `GoalScored` event the game sends when a replay ends:
829
+ * its `Scorer` is `{ Name: '', Shortcut: 0, TeamNum: 0 }`.
830
+ */
831
+ declare function isReplayEndGoal(event: StatsApiEvent): boolean;
789
832
  /** Connection state of the client. */
790
833
  type ConnectionState = 'idle' | 'connecting' | 'open' | 'closed';
791
834
  interface DisconnectInfo {
@@ -914,5 +957,5 @@ declare class RocketLeagueStatsClient {
914
957
  send(command: StatsApiCommand): void;
915
958
  }
916
959
 
917
- export { BALL_UNTOUCHED_TEAM_NUM, COMMAND_NAMES, CommandValidationError, ConnectionError, DEFAULT_PACKET_SEND_RATE as D, EVENT_NAMES, ListenerError, MAX_PACKET_SEND_RATE as M, NotConnectedError, PERSPECTIVES, RocketLeagueStatsClient, STATS_API_INI_FILES as S, StatsApiError, Team, STATS_API_SECTION as a, allOf, arrayOf, ensureStatsApiEnabled as b, findStatsApiIni as c, commandDataGuards, RocketLeagueStatsClient as default, editPacketSendRate as e, eventDataGuards, findRocketLeagueInstallDirs as f, isBallHitData, isBallLastTouch, isBoolean, isBoostPickupData, isChangePOVData, isClockUpdatedSecondsData, isCommandData, isCommandName, isCountdownBeginData, isCrossbarHitData, isEventData, isEventName, isFocus, isGame, isGameBall, isGameTeam, isGoalReplayEndData, isGoalReplayStartData, isGoalReplayWillEndData, isGoalScoredData, isInteger, isLoadReplayData, isMatchCreatedData, isMatchDestroyedData, isMatchEndedData, isMatchInitializedData, isMatchPausedData, isMatchUnpausedData, isNumber, isPerspective, isPlayer, isPlayerJoinedData, isPlayerLeftData, isPlayerRef, isPodiumStartData, isRawEventEnvelope, isRecord, isReplayCreatedData, isRoundStartedData, isSeekReplayData, isSetGameSpeedData, isSetHUDVisibilityData, isSetMatchPausedData, isStatfeedEventData, isStatsApiCommand, isStatsApiEvent, isString, isUpdateStateData, isVector, oneOf, optional, parseEventMessage, statsApiIniPaths as s, shape };
918
- export type { AnyEventListener, BallHitData, BallLastTouch, BaseEventData, BoostPickupData, BoostType, ChangePOVData, ClientEventName, ClientListenerMap, ClockUpdatedSecondsData, CommandDataMap, CommandName, ConnectionState, CountdownBeginData, CrossbarHitData, DisconnectInfo, EnsureStatsApiOptions, EnsureStatsApiResult, EventDataMap, EventName, Focus, Game, GameBall, GameEventListener, GameTeam, GoalReplayEndData, GoalReplayStartData, GoalReplayWillEndData, GoalScoredData, Guard, GuardedType, LifecycleListenerMap, LoadReplayData, MatchCreatedData, MatchDestroyedData, MatchEndedData, MatchInitializedData, MatchPausedData, MatchUnpausedData, PacketSendRateEdit as P, ParseFailure, ParseFailureReason, ParseOptions, ParseResult, ParseSuccess, Perspective, Player, PlayerJoinedData, PlayerLeftData, PlayerRef, PodiumStartData, PrimaryId, RawEventEnvelope, ReconnectInfo, ReconnectOptions, ReplayCreatedData, RoundStartedData, SeekReplayData, SetGameSpeedData, SetHUDVisibilityData, SetMatchPausedData, ShapeSpec, StatEventName, StatfeedEventData, StatsApiClientOptions, StatsApiCommand, StatsApiCommandOf, StatsApiEvent, StatsApiEventOf, TeamNum, Unsubscribe, UpdateStateData, Vector, WebSocketConstructor, WebSocketLike };
960
+ export { BALL_UNTOUCHED_TEAM_NUM, COMMAND_NAMES, CommandValidationError, ConnectionError, DEFAULT_PACKET_SEND_RATE as D, EVENT_NAMES, ListenerError, MAX_PACKET_SEND_RATE as M, NotConnectedError, PERSPECTIVES, RocketLeagueStatsClient, STATS_API_INI_FILES as S, StatsApiError, Team, STATS_API_SECTION as a, allOf, arrayOf, ensureStatsApiEnabled as b, byName, byShortcut, byStat, byTeam, findStatsApiIni as c, commandDataGuards, RocketLeagueStatsClient as default, editPacketSendRate as e, eventDataGuards, findRocketLeagueInstallDirs as f, isBallHitData, isBallLastTouch, isBoolean, isBoostPickupData, isChangePOVData, isClockUpdatedSecondsData, isCommandData, isCommandName, isCountdownBeginData, isCrossbarHitData, isEventData, isEventName, isFocus, isGame, isGameBall, isGameTeam, isGoalReplayEndData, isGoalReplayStartData, isGoalReplayWillEndData, isGoalScoredData, isInteger, isLoadReplayData, isMatchCreatedData, isMatchDestroyedData, isMatchEndedData, isMatchInitializedData, isMatchPausedData, isMatchUnpausedData, isNumber, isPerspective, isPlayer, isPlayerJoinedData, isPlayerLeftData, isPlayerRef, isPodiumStartData, isRawEventEnvelope, isRecord, isReplayCreatedData, isReplayEndGoal, isRoundStartedData, isSeekReplayData, isSetGameSpeedData, isSetHUDVisibilityData, isSetMatchPausedData, isStatfeedEventData, isStatsApiCommand, isStatsApiEvent, isString, isUpdateStateData, isVector, oneOf, optional, parseEventMessage, statsApiIniPaths as s, shape, sortPlayers };
961
+ export type { AnyEventListener, BallHitData, BallLastTouch, BaseEventData, BoostPickupData, BoostType, ChangePOVData, ClientEventName, ClientListenerMap, ClockUpdatedSecondsData, CommandDataMap, CommandName, ConnectionState, CountdownBeginData, CrossbarHitData, DisconnectInfo, EnsureStatsApiOptions, EnsureStatsApiResult, EventDataMap, EventName, Focus, Game, GameBall, GameEventListener, GameTeam, GoalReplayEndData, GoalReplayStartData, GoalReplayWillEndData, GoalScoredData, Guard, GuardedType, LifecycleListenerMap, LoadReplayData, MatchCreatedData, MatchDestroyedData, MatchEndedData, MatchInitializedData, MatchPausedData, MatchUnpausedData, PacketSendRateEdit as P, ParseFailure, ParseFailureReason, ParseOptions, ParseResult, ParseSuccess, Perspective, Player, PlayerComparator, PlayerJoinedData, PlayerLeftData, PlayerRef, PlayerStat, PodiumStartData, PrimaryId, RawEventEnvelope, ReconnectInfo, ReconnectOptions, ReplayCreatedData, RoundStartedData, SeekReplayData, SetGameSpeedData, SetHUDVisibilityData, SetMatchPausedData, ShapeSpec, SortOrder, StatEventName, StatfeedEventData, StatsApiClientOptions, StatsApiCommand, StatsApiCommandOf, StatsApiEvent, StatsApiEventOf, TeamNum, Unsubscribe, UpdateStateData, Vector, WebSocketConstructor, WebSocketLike };
package/dist/index.js CHANGED
@@ -12,6 +12,10 @@ import {
12
12
  Team,
13
13
  allOf,
14
14
  arrayOf,
15
+ byName,
16
+ byShortcut,
17
+ byStat,
18
+ byTeam,
15
19
  commandDataGuards,
16
20
  eventDataGuards,
17
21
  isBallHitData,
@@ -52,6 +56,7 @@ import {
52
56
  isRawEventEnvelope,
53
57
  isRecord,
54
58
  isReplayCreatedData,
59
+ isReplayEndGoal,
55
60
  isRoundStartedData,
56
61
  isSeekReplayData,
57
62
  isSetGameSpeedData,
@@ -66,8 +71,9 @@ import {
66
71
  oneOf,
67
72
  optional,
68
73
  parseEventMessage,
69
- shape
70
- } from "./chunk-IVUQE3LJ.js";
74
+ shape,
75
+ sortPlayers
76
+ } from "./chunk-5ZC7WJPK.js";
71
77
  export {
72
78
  BALL_UNTOUCHED_TEAM_NUM,
73
79
  COMMAND_NAMES,
@@ -82,6 +88,10 @@ export {
82
88
  Team,
83
89
  allOf,
84
90
  arrayOf,
91
+ byName,
92
+ byShortcut,
93
+ byStat,
94
+ byTeam,
85
95
  commandDataGuards,
86
96
  RocketLeagueStatsClient as default,
87
97
  eventDataGuards,
@@ -123,6 +133,7 @@ export {
123
133
  isRawEventEnvelope,
124
134
  isRecord,
125
135
  isReplayCreatedData,
136
+ isReplayEndGoal,
126
137
  isRoundStartedData,
127
138
  isSeekReplayData,
128
139
  isSetGameSpeedData,
@@ -137,6 +148,7 @@ export {
137
148
  oneOf,
138
149
  optional,
139
150
  parseEventMessage,
140
- shape
151
+ shape,
152
+ sortPlayers
141
153
  };
142
154
  //# sourceMappingURL=index.js.map
package/dist/node.cjs CHANGED
@@ -243,6 +243,10 @@ __export(node_exports, {
243
243
  Team: () => Team,
244
244
  allOf: () => allOf,
245
245
  arrayOf: () => arrayOf,
246
+ byName: () => byName,
247
+ byShortcut: () => byShortcut,
248
+ byStat: () => byStat,
249
+ byTeam: () => byTeam,
246
250
  commandDataGuards: () => commandDataGuards,
247
251
  default: () => RocketLeagueStatsClient,
248
252
  editPacketSendRate: () => editPacketSendRate,
@@ -288,6 +292,7 @@ __export(node_exports, {
288
292
  isRawEventEnvelope: () => isRawEventEnvelope,
289
293
  isRecord: () => isRecord,
290
294
  isReplayCreatedData: () => isReplayCreatedData,
295
+ isReplayEndGoal: () => isReplayEndGoal,
291
296
  isRoundStartedData: () => isRoundStartedData,
292
297
  isSeekReplayData: () => isSeekReplayData,
293
298
  isSetGameSpeedData: () => isSetGameSpeedData,
@@ -303,6 +308,7 @@ __export(node_exports, {
303
308
  optional: () => optional,
304
309
  parseEventMessage: () => parseEventMessage,
305
310
  shape: () => shape,
311
+ sortPlayers: () => sortPlayers,
306
312
  statsApiIniPaths: () => statsApiIniPaths
307
313
  });
308
314
  module.exports = __toCommonJS(node_exports);
@@ -642,6 +648,37 @@ function parseEventMessage(raw, options = {}) {
642
648
  return { ok: true, event: { Event: event, Data: data } };
643
649
  }
644
650
 
651
+ // src/sort.ts
652
+ var direction = (order) => order === "asc" ? 1 : -1;
653
+ function byTeam(order = "asc") {
654
+ return (a, b) => (a.TeamNum - b.TeamNum) * direction(order);
655
+ }
656
+ function byShortcut(order = "asc") {
657
+ return (a, b) => (a.Shortcut - b.Shortcut) * direction(order);
658
+ }
659
+ function byName(order = "asc") {
660
+ return (a, b) => a.Name.localeCompare(b.Name, void 0, { sensitivity: "base" }) * direction(order);
661
+ }
662
+ function byStat(stat, order = "desc") {
663
+ return (a, b) => {
664
+ const x = a[stat];
665
+ const y = b[stat];
666
+ if (x === void 0 || y === void 0) return x === y ? 0 : x === void 0 ? 1 : -1;
667
+ return (x - y) * direction(order);
668
+ };
669
+ }
670
+ function sortPlayers(players, ...comparators) {
671
+ const copy = [...players];
672
+ if (comparators.length === 0) return copy;
673
+ return copy.sort((a, b) => {
674
+ for (const compare of comparators) {
675
+ const result = compare(a, b);
676
+ if (result !== 0) return result;
677
+ }
678
+ return 0;
679
+ });
680
+ }
681
+
645
682
  // src/errors.ts
646
683
  var StatsApiError = class extends Error {
647
684
  name = "StatsApiError";
@@ -676,6 +713,11 @@ var ListenerError = class extends StatsApiError {
676
713
 
677
714
  // src/client.ts
678
715
  var WS_OPEN = 1;
716
+ function isReplayEndGoal(event) {
717
+ if (event.Event !== "GoalScored") return false;
718
+ const { Name, Shortcut, TeamNum } = event.Data.Scorer;
719
+ return Name === "" && Shortcut === 0 && TeamNum === 0;
720
+ }
679
721
  function isNodeRuntime() {
680
722
  const proc = globalThis.process;
681
723
  return typeof proc?.versions?.node === "string";
@@ -691,6 +733,8 @@ var RocketLeagueStatsClient = class {
691
733
  #autoConnect;
692
734
  #reconnect;
693
735
  #validateEvents;
736
+ #ignoreReplayEndGoal;
737
+ #playerComparators;
694
738
  #WebSocket;
695
739
  #socket = null;
696
740
  #state = "idle";
@@ -704,6 +748,8 @@ var RocketLeagueStatsClient = class {
704
748
  this.url = options.url ?? `${protocol}://${options.host ?? "127.0.0.1"}:${options.port ?? 49124}`;
705
749
  this.#autoConnect = options.autoConnect ?? true;
706
750
  this.#validateEvents = options.validateEvents ?? true;
751
+ this.#ignoreReplayEndGoal = options.ignoreReplayEndGoal ?? true;
752
+ this.#playerComparators = options.sortPlayers === void 0 ? [] : [options.sortPlayers].flat();
707
753
  this.#WebSocket = options.WebSocket ?? globalThis.WebSocket;
708
754
  const reconnect = options.reconnect ?? true;
709
755
  this.#reconnect = reconnect === false ? null : {
@@ -842,6 +888,10 @@ var RocketLeagueStatsClient = class {
842
888
  return;
843
889
  }
844
890
  const { event } = result;
891
+ if (this.#ignoreReplayEndGoal && isReplayEndGoal(event)) return;
892
+ if (this.#playerComparators.length > 0 && event.Event === "UpdateState" && Array.isArray(event.Data.Players)) {
893
+ event.Data.Players = sortPlayers(event.Data.Players, ...this.#playerComparators);
894
+ }
845
895
  this.#dispatch(event.Event, [event.Data, event]);
846
896
  for (const listener of [...this.#anyListeners]) {
847
897
  this.#invoke("*", listener, [event]);
@@ -983,6 +1033,10 @@ init_config();
983
1033
  Team,
984
1034
  allOf,
985
1035
  arrayOf,
1036
+ byName,
1037
+ byShortcut,
1038
+ byStat,
1039
+ byTeam,
986
1040
  commandDataGuards,
987
1041
  editPacketSendRate,
988
1042
  ensureStatsApiEnabled,
@@ -1027,6 +1081,7 @@ init_config();
1027
1081
  isRawEventEnvelope,
1028
1082
  isRecord,
1029
1083
  isReplayCreatedData,
1084
+ isReplayEndGoal,
1030
1085
  isRoundStartedData,
1031
1086
  isSeekReplayData,
1032
1087
  isSetGameSpeedData,
@@ -1042,6 +1097,7 @@ init_config();
1042
1097
  optional,
1043
1098
  parseEventMessage,
1044
1099
  shape,
1100
+ sortPlayers,
1045
1101
  statsApiIniPaths
1046
1102
  });
1047
1103
  //# sourceMappingURL=node.cjs.map