From e107d6d448be03a2bdb703106c38f20e1dbefc78 Mon Sep 17 00:00:00 2001 From: tsa96 Date: Mon, 15 Apr 2024 12:01:03 +0100 Subject: [PATCH] feat: RegisterHUDPanelForGamemode :D --- scripts/util/register-for-gamemodes.ts | 167 +++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 scripts/util/register-for-gamemodes.ts diff --git a/scripts/util/register-for-gamemodes.ts b/scripts/util/register-for-gamemodes.ts new file mode 100644 index 00000000..cd817826 --- /dev/null +++ b/scripts/util/register-for-gamemodes.ts @@ -0,0 +1,167 @@ +/** + * Pass an event that should be only registered when playing certain gamemodes. + * + * This function will register/unregister the provided events handler when gamemodes change. + * + * It returns a function that will clean up both event handlers. It's unlikely we'll ever need to use it, + * so usually you can just discard it. + * + * @example + * ```ts + * // In the static initializer block for some panel's class, call the onUpdate function on this class + * // whenever the 'HudProcessInput' event is fired, ONLY if the current gamemode is Gamemode.DEFRAG. + * static { + * RegisterEventForGamemodes( + * [GameMode.DEFRAG], + * 'HudProcessInput', + * $.GetContextPanel(), + * this.onUpdate.bind(this) + * ); + * } + * ``` + * @param gamemodes - Gamemodes to register the callback for. Handler is unregistered for any other modes. + * @param event - Event name + * @param context - Panel context + * @param callbackFn - Event callback + * @returns Cleanup function that unregisters both event handlers + */ +function RegisterEventForGamemodes( + gamemodes: number[], + event: string, + context: string | Panel, + callbackFn: (...args: unknown[]) => void +): () => void { + let innerHandle: number | undefined; + const outerHandle = $.RegisterForUnhandledEvent('LevelInitPostEntity', () => { + // @ts-expect-error - TODO: Typings for this API + if (!innerHandle && gamemodes.includes(GameModeAPI.GetCurrentGameMode())) { + innerHandle = $.RegisterEventHandler(event, context, callbackFn); + } else if (innerHandle) { + $.UnregisterEventHandler(event, context, innerHandle); + innerHandle = undefined; + } + }); + + return () => { + if (innerHandle) { + $.UnregisterEventHandler(event, context, innerHandle); + } + + $.UnregisterForUnhandledEvent('LevelInitPostEntity', outerHandle); + }; +} + +interface RegisterHUDPanelForGamemodeOptions { + context: object; + contextPanel: Panel; + gamemodes: number[]; + onLoad?: Func; + handledEvents?: Array<{ event: string; callback: Func; contextPanel: Panel }>; + unhandledEvents?: Array<{ event: string; callback: Func }>; +} + +/** + * Mark a panel as being only active in the given gamemodes. + * + * The panel will be set to `visible=true` in the given modes, and `false` in all others. + * + * Any events provided will be registered at `LevelInitPostEntity` when the given a map is launched in the provided + * modes, and unregistered in any other modes. + * + * An optional `onLoad` function will be called whenever a launched in the provided modes. + * + * You must provide the current JS context with `context`, and don't need to use `.bind(this)` on any functions you pass. + * + * @example + * // Simplest usage, only visible in SURF. + * class MyEpicSurfPanel { + * static { + * RegisterHUDPanelForGamemode({ + * gamemodes: [Gamemode.SURF], + * context: this, + * contextPanel: $.GetContextPanel() + * }) + * } + * } + * + * // More advanced example. + * class MyFuckedUpBhopPanel { + * static { + * RegisterHUDPanelForGamemode({ + * gamemodes: [Gamemode.BHOP], + * context: this, + * contextPanel: $.GetContextPanel(), + * onLoad: this.setup, + * handledEvents: [{ + * event: 'HudProcessInput', + * callback: this.onUpdate, + * contextPanel: $.GetContextPanel() + * }] + * }) + * } + * + * static setup() { + * // ... + * } + * + * static onUpdate() { + * // ... + * } + * } + * + * @returns Cleanup function that unregisters all event handlers + */ +function RegisterHUDPanelForGamemode({ + context, + contextPanel, + onLoad, + gamemodes, + handledEvents, + unhandledEvents +}: RegisterHUDPanelForGamemodeOptions): () => void { + if (!(gamemodes?.length > 0)) { + throw new Error('RegisterHUDPanelForGamemode: no gamemode provided'); + } + + let handles: Array<{ event: string; handle: number; contextPanel?: Panel }>; + + const unregister = () => + (handles ?? []).forEach(({ event, handle, contextPanel }) => + contextPanel === undefined + ? $.UnregisterForUnhandledEvent(event, handle) + : $.UnregisterEventHandler(event, contextPanel, handle) + ); + + const handle = $.RegisterForUnhandledEvent('LevelInitPostEntity', () => { + unregister(); + handles = []; + + // @ts-expect-error - TODO: Typings for this API + if (gamemodes.includes(GameModeAPI.GetCurrentGameMode())) { + contextPanel.visible = true; + onLoad?.call(context); + + for (const { event, callback } of unhandledEvents ?? []) { + handles.push({ + event, + handle: $.RegisterForUnhandledEvent(event, callback.bind(context)) + }); + } + + for (const { event, contextPanel, callback } of handledEvents ?? []) { + handles.push({ + event, + contextPanel, + handle: $.RegisterEventHandler(event, contextPanel, callback.bind(context)) + }); + } + } else { + contextPanel.visible = false; + } + }); + + return () => { + unregister(); + $.UnregisterForUnhandledEvent('LevelInitPostEntity', handle); + }; +}