[Linux-only] basically bloxstap for sober
1import type {
2 CurrentStateAPI,
3 fflagList,
4 fflagValue,
5 SoberConfig
6} from "./types";
7import { currentStateManager } from "./CurrentState";
8import {
9 eventCollector,
10 type EventType,
11 type EventCallback,
12 type EventSubscription
13} from "./EventCollector";
14
15type PluginMeta = {
16 name: string;
17 id: string;
18 forceEnable?: boolean;
19 /** the lower, the earlier configs are applied. higher means more priority over end value */
20 configPrio?: number;
21 description?: string;
22};
23
24export class Plugin {
25 public name: string;
26 public id: string;
27 public forceEnable: boolean;
28 public configPrio: number;
29 public description: string;
30
31 private runtimeSetFFlags: fflagList = {};
32 private runtimeSetSoberConfig: SoberConfig = {};
33
34 constructor({ name, id, forceEnable, configPrio, description }: PluginMeta) {
35 this.description = description || `The "${name}" plugin`;
36 this.name = name;
37 this.id = id;
38 this.forceEnable = forceEnable ?? false;
39 this.configPrio = configPrio ?? 0;
40 this.runtimeSetFFlags = {};
41 }
42
43 get fflags(): fflagList {
44 return this.runtimeSetFFlags;
45 }
46
47 get soberConfig(): SoberConfig {
48 return this.runtimeSetSoberConfig;
49 }
50
51 public setFFlag(flag: string, value: fflagValue) {
52 this.runtimeSetFFlags[flag] = value;
53 }
54
55 public setSoberConfigOption<K extends keyof SoberConfig>(
56 opt: K,
57 value: NonNullable<SoberConfig[K]>
58 ) {
59 this.runtimeSetSoberConfig[opt] = value;
60 }
61
62 /**
63 * Subscribe to events using the new event system
64 */
65 public on<T extends EventType>(
66 eventType: T,
67 callback: EventCallback<T>
68 ): EventSubscription {
69 return eventCollector.on(eventType, callback);
70 }
71
72 /**
73 * Legacy method for backward compatibility - maps to new event system
74 */
75 public hookLog = {
76 GAME_JOIN: (callback: EventCallback<"GAME_JOIN">) =>
77 this.on("GAME_JOIN", callback),
78 GAME_LEAVE: (callback: EventCallback<"GAME_LEAVE">) =>
79 this.on("GAME_LEAVE", callback),
80 JOIN_LEAVE: (
81 callback: EventCallback<"PLAYER_JOIN" | "PLAYER_LEAVE">
82 ) => {
83 // Subscribe to both player join and leave events
84 const joinSub = this.on(
85 "PLAYER_JOIN",
86 callback as EventCallback<"PLAYER_JOIN">
87 );
88 const leaveSub = this.on(
89 "PLAYER_LEAVE",
90 callback as EventCallback<"PLAYER_LEAVE">
91 );
92
93 return {
94 unsubscribe: () => {
95 joinSub.unsubscribe();
96 leaveSub.unsubscribe();
97 }
98 };
99 },
100 BLOXSTRAP: (callback: EventCallback<"BLOXSTRAP_RPC">) =>
101 this.on("BLOXSTRAP_RPC", callback)
102 };
103
104 get currentState(): CurrentStateAPI {
105 return {
106 getCurrentState: () => currentStateManager.getCurrentState(),
107 onStateChange: (callback) =>
108 currentStateManager.onStateChange(callback),
109 isInGame: () => currentStateManager.isInGame(),
110 getPlaceId: () => currentStateManager.getPlaceId(),
111 getJobId: () => currentStateManager.getJobId()
112 };
113 }
114}
115
116let pluginsRegistered: Plugin[] = [];
117let pluginMetadatas: PluginMeta[] = [];
118let pluginsRegisterFuncs: ((forceEnable: string[], forceDisable: string[]) => void)[] = [];
119
120export function registerPlugin(
121 details: PluginMeta,
122 initFunc: (plugin: Plugin) => void
123) {
124 pluginMetadatas.push(details);
125 pluginsRegisterFuncs.push(async (forceEnable, forceDisable) => {
126 if (!details.forceEnable && !forceEnable.includes(details.id)) return;
127 if (forceDisable.includes(details.id)) return;
128 if (!details.configPrio) details.configPrio = 0;
129 while (true) {
130 if (
131 !pluginsRegistered.find(
132 (a) => a.configPrio === details.configPrio
133 )
134 )
135 break;
136 details.configPrio++;
137 }
138 const plugin = new Plugin(details);
139 try {
140 await initFunc(plugin);
141 pluginsRegistered.push(plugin);
142 console.log(
143 `[api/Plugin] PluginInit(${plugin.id}): "${plugin.name}" successfully initalized`
144 );
145 } catch (error) {
146 console.error(
147 `[api/Plugin] PluginInitError(${plugin.id}): "${plugin.name}" errored:`,
148 error
149 );
150 }
151 });
152}
153
154export async function registerPluginsAllFinal(forceEnable: string[], forceDisable: string[]) {
155 for (const f of pluginsRegisterFuncs) {
156 await f(forceEnable, forceDisable);
157 }
158}
159
160export function getPlugins(): Plugin[] {
161 return pluginsRegistered;
162}
163
164export function getPluginInfos(): PluginMeta[] {
165 return pluginMetadatas;
166}