interface ServerChatRoomMessage { MBCHC_ID?: number } namespace BCE { interface Matcher { Tester: RegExp Criteria?: { TargetIsPlayer?: boolean SenderIsPlayer?: boolean DictionaryMatchers?: Array> } } interface Trigger { Event: string Type: 'Emote' | 'Activity' | 'Action' Matchers: Matcher[] } interface Patcher { timer: number | undefined patches: Array<[RegExp, string]> cfs: {[k in 'anim' | 'pose']: () => Iterable} gen: (comp_func: () => Iterable) => (this: Optional) => undefined copy(t: Trigger): Trigger patch(): true | undefined } } type SUBCOMMANDS = { [k: string]: { desc: string args?: {[n: string]: OBJ} cb: (mbchc: Window['MBCHC'], args: string[], __: unknown, ___?: unknown) => void } }; interface Window { MBCHC: { loader: () => void DO_DATA: { zones: OBJ verbs: { [verb: string]: { [zone: string]: { self: string[] others: string[] } } } } MAP_ACTIONS: { [verb: string]: { [zones: string]: { all?: string self?: string others?: string } } } MAP_ZONES: {[zone: string]: string[]} LOADED: boolean NEXT_MESSAGE: number LOG_MESSAGES: boolean LAST_HACKED: number | undefined version: string VERSION: string Settings: Settings.Methods TZ: TZ_Cache SUBCOMMANDS_MBCHC: SUBCOMMANDS H: InputHistory U: Utils AUTOHACK_ENABLED: boolean RE_PREF_ACTIVITY_ME: RegExp RE_PREF_ACTIVITY: RegExp RE_ACT_CIDS: RegExp RE_LAST_WORD: RegExp RE_LAST_LETTER: RegExp RE_ACTIVITY: RegExp UTC_OFFSET: number calculate_maps(): void normalise_message(text: string, options?: OBJ): string donate_data(target: string): void run_activity(char: Character, ag: AssetGroupItemName, action: ActivityName): void send_activity(message: string): void set_timezone(args: string[]): number | undefined command_mbchc(this: Optional, args: string, msg: string, parsed: string[]): undefined command_activity(this: Optional, args: string, msg: string, parsed: string[]): undefined command_do(this: Optional, args: string, msg: string, parsed: string[]): undefined } bcModSdk?: import('./node_modules/bondage-club-mod-sdk/dist/bcmodsdk.d.ts').ModSDKGlobalAPI; FBC_VERSION?: string bce_ActivityTriggers?: BCE.Trigger[] bce_EventExpressions?: OBJ } type OBJ = Record; type FUN = (...args: never[]) => unknown; /** * Stands for "Value or Function". Not remotely ideal, but the best I can come up with. */ type VOF = undefined | boolean | number | bigint | string | symbol | OBJ | Set | Map | FP.Interval | F; /** * I can write Haskell in every language. * I was unbelievably tempted to go with emojis here, but I'm not a monster of this caliber. * @see https://8fw.me/hell/a8aaaefcb6e30ec4b2e398c70d49b72a6b53232f.png */ namespace FP { interface Interval { proxy: object // eslint-disable-line @typescript-eslint/ban-types min: number max: number mini: boolean maxi: boolean has(_: unknown, x: string): boolean } interface Pipeline { proxy: PipelineProxy me(iterable: Iterable): PipelineProxy [Symbol.iterator](): Iterator rdc(initial: R, func: (accumulator: R, value: T) => R): R any(func: (v: T) => boolean): boolean all(func: (v: T) => boolean): boolean map(func: (value: T) => R): PipelineProxy sel(func: (value: T) => boolean): PipelineProxy } interface PipelineProxy extends Pipeline { [i: number]: T | undefined } /** * Creates an iteration pipeline. */ type P = (iterable: Iterable) => PipelineProxy; /** * A silly helper to kinda c[au]rry values. Basically equivalent to: * `const a = value; return func(a)` * Stands for "Carry, Use, Return". */ type cur = (value: T, func: (value: T) => R) => R; /** * Convert `null` to `undefined`. */ // eslint-disable-next-line @typescript-eslint/ban-types type n2u = (value: T | undefined | null) => T | undefined; /** * Enumerate keys of an object. Type-safe, also shorter. */ type enu = (object: O) => Array; /** * Like `carry()` but does nothing if `value` is `null` or `undefined`. * Something like an `Option<>` when you want to just pass `undefined` along, but * run a function on actual values. Also coverts `null` to `undefined`. */ // eslint-disable-next-line @typescript-eslint/ban-types type val = (value: T | null | undefined, func: (value: T) => R) => R | undefined; /** * `+` as a function */ type add = (x: number, y: number) => number; /** * `-` as a function */ type sub = (x: number, y: number) => number; /** * `>` as a function */ type cgt = (x: number, y: number) => boolean; /** * `>=` as a function */ type cge = (x: number, y: number) => boolean; /** * `<` as a function */ type clt = (x: number, y: number) => boolean; /** * `<=` as a function */ type cle = (x: number, y: number) => boolean; /** * Converts a string into a base 10 integer. Not only is it shorter than `Number.parseInt`, * it also converts `NaN` to `undefined`, because I find it much easier to only have * one nil value for everything. */ type int = (text: string) => number | undefined; /** * This is a type assertion helper for Typescript. Probably not very useful in general. */ type fun = (func: unknown) => func is F; /** * Takes anything, ignores non-functions, calls functions with supplied parameters. */ type run = (functions_or_values: Array>, ...args: Parameters) => true; /** * Takes anything, ignores non-functions, calls functions with `undefined` as a single parameter. */ type yes = (...args: Array unknown>>) => true; /** * Takes anything, ignores non-functions, calls functions with a single provided parameter. * Always returns the parameter itself. */ type mut = (value: T, ...args: Array unknown>>) => T; /** * `delete` as a function */ // eslint-disable-next-line @typescript-eslint/ban-types type del = (object: T, property: keyof T) => T; /** * Short for `assert`. Throws, if value is `undefined`, or if `condition(value)` is false. */ type ass = (error: string, value: T | undefined, condition?: (value: T) => boolean) => T | never; /** * Casts value as a given prototype or returns undefined. */ type asa = (prototype: new () => T, value: unknown) => T | undefined; /** * `catch` as a function. Short for `rescue`. */ type rsc = (operation: (_: undefined) => unknown, exception_handler: (error: unknown) => unknown) => true; /** * Returns a proxy for `in` operator. * Use it like `2 in rng(0, 4)`. */ // eslint-disable-next-line @typescript-eslint/ban-types type rng = (min: number, max: number, min_inclusive?: boolean, max_inclusive?: boolean) => object; /** * Something slightly better than `Boolean()`. * True is `null`, `undefined`, `false`, empty strings, sets, maps, arrays and objects. * Short for "empty". */ type m_t = (value: unknown) => boolean; /** * `while()` as a function. Executes the second callback while the first one is true. * Always bound by max number of iterations. * Returns the last value from the action or undefined if the action never ran. */ type loo = (max: number, condition: (_: undefined) => boolean, action: (_: undefined) => T) => T | undefined; } namespace SDK { type GDPT = import('./node_modules/bondage-club-mod-sdk/dist/bcmodsdk.d.ts').GetDotedPathType; type Void = (...args: Parameters) => unknown; type Hook = (name: F, hook: Void>) => () => unknown; } namespace Cons { type MS = {w: 'warn', i: 'info', d: 'debug', l: 'log'}; type E = (error: unknown) => true; type F = (message: string) => true; interface Wrap { readonly ms: MS e: E w: F i: F d: F l: F gen: (m: keyof MS) => F } } namespace Settings { /** * The whole `Player.OnlineSettings`. */ type V0 = PlayerOnlineSettings & {MBCHC?: {timezones?: Record}}; /** * Specifically `MBCHC` inside `Player.ExtensionSettings`. */ interface V1 { TZ: Record } interface Methods { migrate_0_1(v0: V0): true save(func?: (v1: V1) => unknown): true replace(new_v1: V1): true 'purge!'(): true get v0(): V0 | undefined get v1(): V1 } } /** * We need a place to cache the timezones instead of the `Character` object itself. */ interface TZ_Cache { map: Map RE: RegExp parse(description: string | undefined): number | undefined memo(member_number: number, description?: string | undefined): number | undefined lookup(character: Character): number | undefined } interface Utils { remove_loader_hook: (() => unknown) | undefined RE: {SPACES: RegExp, REL: {L: RegExp, R: RegExp}, '@': [RegExp, RegExp]} RGB: {Polly: string, Mute: string} ACT: string, get crc(): Character[] get ic(): HTMLTextAreaElement | undefined cid(character: Character): number | undefined dn(character: Character): string current(): string style(query: string, func: (s: CSSStyleDeclaration) => T): T | undefined inform(html: string): true report(error: unknown): true /** * Splits a string into words by continuous whitespace sequences. Some examples: * ``` * "" => [""] * " " => ["", ""] * "f g" => ["f", "g"] * " f g " => ["", "f", "g", ""] * ``` */ split(text: string): string[] abs2char(index: number): Character rel2char(target: string): Character cid2char(cid: number): Character target2char(target: string): Character mkdiv(html?: string): HTMLDivElement bell(): true targets(me2?: boolean, check_perms?: boolean): Set complete_mbchc(this: Optional): undefined complete_do_target(actions: {self: unknown, others: unknown}): Set complete_do(this: Optional): undefined replace_me(_match: string, _offset: number, whole: string): string //pad_chat(chat: HTMLDivElement): undefined } interface Complete { S_OPTS: {behavior: 'instant'} /** * The suggestions panel. * Its structure is (outer div) -> (container div) -> (multiple suggestion div elements) */ e: HTMLDivElement /** * The container div. */ get div(): Element | undefined /** * Longest common prefix, or an empty string for an empty set. */ lcp(candidates: Set): string /** * !WARNING! Mutate the given set in accordance with the input. * Returns the input with completion done and removes all failed candidates. * If the set is empty, the completion failed and the result is the unmodified input. * If the set has more than one value, these are all new candidates, and the input was completed with the lcp. * Otherwise, the only successful candidate will remain in the set and the input was completed to it. */ complete_word(input: string, candidates: Set, ignore_case?: boolean | undefined): string /** * Show the suggestions to the user. */ hint(candidates: Set): true /** * Returns false if the suggestion panel is visible. */ get hidden(): boolean /** * Makes the suggestions panel disappear. */ hide(): true /** * The whole deal. Will read and modify the chat input window. * Takes a callback that will receive current input split into words * and should return all possible candidates for the word being completed. * Note: if the input ends with whitespace, the last word will be empty. */ complete(func: (words: string[]) => Set, ignore_case?: boolean | undefined): true /** * Case-insensitive complete. */ icomplete(func: (words: string[]) => Set): true } /** * So, this is what happens. We have two modes: input mode and history mode. In the history mode the element is read-only. * In the input mode: * If the input is empty, we just scroll the history as usual * Otherwise, we build a history set using the input as prefix * The history set filters through the history, keeping only unique lines that start with the prefix along with indices * But we exclude lines that match the input exactly * It keeps the original input in a separate place too * If the set is empty, we bell out * Otherwise, we enter the history mode and invoke the first search * In the history mode: * We search up or down, using the index as a starting point for the next match, treating the set as a ring * If the found line is the same as the current line, we bell out * Upon finding a next match, we replace the input with its text and set the index appropriately * We exit the history mode using the InputChat keydown handler, on Tab, Escape or Enter * Escape restores the saved input, discarding the history line * Tab keeps the current text and unlocks the element, allowing it to be edited * Enter keeps the current text and sends it as the message as usual */ interface InputHistory { /** * Whether the chat log is scrolled to the end when we enter history mode. */ bottom: boolean | undefined /** * The initial user input when we enter the history mode. */ input: string | undefined /** * The indices of the lines that match the prefix. */ ids: Set | undefined /** * Enter the history mode. */ enter(textarea: HTMLTextAreaElement, input: string, bottom: boolean, ids: Set): true /** * Exit the history mode and optionally restore original input. */ exit(textarea: HTMLTextAreaElement, restore_input: boolean): true } // FIXME spread around readonly where appropriate