Compare commits
6 Commits
c3d47fec77
...
07c1fad5f3
Author | SHA1 | Date | |
---|---|---|---|
07c1fad5f3 | |||
78e2d6ebda | |||
9a646716bf | |||
b9b1226e60 | |||
fb700c91cf | |||
ee85533bd1 |
21
ambient.d.ts
vendored
21
ambient.d.ts
vendored
|
@ -66,9 +66,6 @@ interface Window {
|
||||||
VERSION: string
|
VERSION: string
|
||||||
Settings: Settings.Methods
|
Settings: Settings.Methods
|
||||||
TZ: TZ_Cache
|
TZ: TZ_Cache
|
||||||
SUBCOMMANDS_MBCHC: SUBCOMMANDS
|
|
||||||
H: InputHistory
|
|
||||||
U: Utils
|
|
||||||
AUTOHACK_ENABLED: boolean
|
AUTOHACK_ENABLED: boolean
|
||||||
RE_PREF_ACTIVITY_ME: RegExp
|
RE_PREF_ACTIVITY_ME: RegExp
|
||||||
RE_PREF_ACTIVITY: RegExp
|
RE_PREF_ACTIVITY: RegExp
|
||||||
|
@ -349,7 +346,9 @@ interface Utils {
|
||||||
complete_do_target(actions: {self: unknown, others: unknown}): Set<string>
|
complete_do_target(actions: {self: unknown, others: unknown}): Set<string>
|
||||||
complete_do(this: Optional<ICommand, 'Tag'>): undefined
|
complete_do(this: Optional<ICommand, 'Tag'>): undefined
|
||||||
replace_me(_match: string, _offset: number, whole: string): string
|
replace_me(_match: string, _offset: number, whole: string): string
|
||||||
//pad_chat(chat: HTMLDivElement): undefined
|
scroll(): true
|
||||||
|
get scrolled(): boolean
|
||||||
|
rescroll(func: (_: undefined) => unknown): true
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Complete {
|
interface Complete {
|
||||||
|
@ -445,15 +444,25 @@ interface InputHistory {
|
||||||
*/
|
*/
|
||||||
ids: Set<number> | undefined
|
ids: Set<number> | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific key handlers.
|
||||||
|
*/
|
||||||
|
key: Record<string, (event: KeyboardEvent, textarea: HTMLTextAreaElement) => true>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or unset the readonly mode on the input.
|
||||||
|
*/
|
||||||
|
icro(textarea: HTMLTextAreaElement, readonly: boolean): true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enter the history mode.
|
* Enter the history mode.
|
||||||
*/
|
*/
|
||||||
enter(textarea: HTMLTextAreaElement, input: string, bottom: boolean, ids: Set<number>): true
|
enter(textarea: HTMLTextAreaElement, input: string, bottom: boolean, ids: Set<number>): true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exit the history mode and optionally restore original input.
|
* Exit the history mode and proc the key event.
|
||||||
*/
|
*/
|
||||||
exit(textarea: HTMLTextAreaElement, restore_input: boolean): true
|
exit(textarea: HTMLTextAreaElement, e: KeyboardEvent): true
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME spread around readonly where appropriate
|
// FIXME spread around readonly where appropriate
|
||||||
|
|
187
mbchc.mjs
187
mbchc.mjs
|
@ -1,5 +1,5 @@
|
||||||
// Take a look at the .d.ts for comments.
|
// Take a look at the .d.ts for comments.
|
||||||
export const version = '107.13.0'
|
export const version = '108.13.1'
|
||||||
const W = window, D = W.document, /**fuck money*/$ = undefined, /**@type {''}*/$S = '', /**@type {{}}*/$O = {}, /**@type {Set<string>}*/$Ss = new Set() // /**@type {readonly []}*/$A = [],
|
const W = window, D = W.document, /**fuck money*/$ = undefined, /**@type {''}*/$S = '', /**@type {{}}*/$O = {}, /**@type {Set<string>}*/$Ss = new Set() // /**@type {readonly []}*/$A = [],
|
||||||
const/**@type {TextDictionaryEntry}*/MISSING_PLAYER_DIALOG = {Tag: 'MISSING TEXT IN "Interface.csv": ', Text: '\u200C'} // Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsey value
|
const/**@type {TextDictionaryEntry}*/MISSING_PLAYER_DIALOG = {Tag: 'MISSING TEXT IN "Interface.csv": ', Text: '\u200C'} // Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsey value
|
||||||
|
|
||||||
|
@ -158,7 +158,9 @@ const/**@type {Utils}*/U = { remove_loader_hook: $, RGB: {Polly: '#81b1e7', Mute
|
||||||
return $Ss
|
return $Ss
|
||||||
})},
|
})},
|
||||||
replace_me: (_, __, whole) => cur(whole.slice(1), t => `${U.ACT}<${U.cid(W.Player)}:>SourceCharacter${t.startsWith('\'') || t.startsWith(' ') ? $S : ' '}`),
|
replace_me: (_, __, whole) => cur(whole.slice(1), t => `${U.ACT}<${U.cid(W.Player)}:>SourceCharacter${t.startsWith('\'') || t.startsWith(' ') ? $S : ' '}`),
|
||||||
//pad_chat: c => loo(100, _ => c.scrollHeight <= c.clientHeight, _ => yes(void c.prepend(U.mkdiv('\u061C')))) && void W.ElementScrollToEnd('TextAreaChatLog')
|
scroll: () => yes(void W.ElementScrollToEnd('TextAreaChatLog')),
|
||||||
|
get scrolled() {return W.ElementIsScrolledToEnd('TextAreaChatLog')},
|
||||||
|
rescroll: f => cur(U.scrolled, s => yes(f, s && U.scroll())),
|
||||||
}
|
}
|
||||||
|
|
||||||
const/**@type {SUBCOMMANDS}*/SUBCOMMANDS_MBCHC = {
|
const/**@type {SUBCOMMANDS}*/SUBCOMMANDS_MBCHC = {
|
||||||
|
@ -178,7 +180,7 @@ const/**@type {Complete}*/C = { S_OPTS: {behavior: 'instant'},
|
||||||
hint: cs => m_t(cs) || yes(_ => {
|
hint: cs => m_t(cs) || yes(_ => {
|
||||||
yes(C.e.style.display = 'block') && C.div?.replaceChildren(...[...cs].sort().reverse().map(U.mkdiv))
|
yes(C.e.style.display = 'block') && C.div?.replaceChildren(...[...cs].sort().reverse().map(U.mkdiv))
|
||||||
W.ElementSetDataAttribute(C.e.id, 'colortheme', W.Player.ChatSettings?.ColorTheme ?? 'Light')
|
W.ElementSetDataAttribute(C.e.id, 'colortheme', W.Player.ChatSettings?.ColorTheme ?? 'Light')
|
||||||
cur(W.ElementIsScrolledToEnd('TextAreaChatLog'), r => yes(void W.ChatRoomResize(false)) && r && void W.ElementScrollToEnd('TextAreaChatLog'))
|
U.rescroll(_ => void W.ChatRoomResize(false))
|
||||||
C.div?.lastElementChild?.scrollIntoView(C.S_OPTS)
|
C.div?.lastElementChild?.scrollIntoView(C.S_OPTS)
|
||||||
}),
|
}),
|
||||||
get hidden() {return C.e.parentElement === null || C.e.style.display === 'none'},
|
get hidden() {return C.e.parentElement === null || C.e.style.display === 'none'},
|
||||||
|
@ -203,8 +205,13 @@ const/**@type {Complete}*/C = { S_OPTS: {behavior: 'instant'},
|
||||||
}
|
}
|
||||||
|
|
||||||
const/**@type {InputHistory}*/H = { input: undefined, ids: undefined, bottom: undefined, // FIXME ids don't need to be a set, but I'm too tired right now
|
const/**@type {InputHistory}*/H = { input: undefined, ids: undefined, bottom: undefined, // FIXME ids don't need to be a set, but I'm too tired right now
|
||||||
enter: (ic, i, b, is) => yes(H.input = i, H.bottom = b, H.ids = is, ic.readOnly = true, val(ic.parentElement?.parentElement?.dataset, d => d['mbchcMode'] = 'h'), b && void W.ElementScrollToEnd('TextAreaChatLog')),
|
key: {
|
||||||
exit: (ic, r) => yes(ic.readOnly = false, val(ic.parentElement?.parentElement?.dataset, d => del(d, 'mbchcMode')), r && val(H.input, i => ic.value = i), val(H.bottom, b => b && void W.ElementScrollToEnd('TextAreaChatLog')), W.ChatRoomLastMessageIndex = W.ChatRoomLastMessage.length)
|
Escape: (_, ic) => yes(val(H.input, i => ic.value = i)),
|
||||||
|
ArrowLeft: (_, ic) => yes(void ic.setSelectionRange(0, 0)),
|
||||||
|
},
|
||||||
|
icro: (ic, ro) => yes(ic.readOnly = ro, val(ic.parentElement?.parentElement?.dataset, d => ro ? d['mbchcMode'] = 'h' : del(d, 'mbchcMode'))),
|
||||||
|
enter: (ic, i, b, is) => yes(H.input = i, H.bottom = b, H.ids = is, H.icro(ic, true), b && U.scroll()),
|
||||||
|
exit: (ic, e) => yes(H.icro(ic, false), H.key[e.key]?.(e, ic), val(H.bottom, b => b && U.scroll()), W.ChatRoomLastMessageIndex = W.ChatRoomLastMessage.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
ass('MBCHC found, aborting loading', W.MBCHC === $)
|
ass('MBCHC found, aborting loading', W.MBCHC === $)
|
||||||
|
@ -222,21 +229,17 @@ const/**@type {SDK.Hook}*/after = (name, f) => mod.hookFunction(name, 0, (na, n)
|
||||||
version, /** Just in case someone used it for anything @deprecated*/VERSION: version,
|
version, /** Just in case someone used it for anything @deprecated*/VERSION: version,
|
||||||
Settings, TZ,
|
Settings, TZ,
|
||||||
|
|
||||||
SUBCOMMANDS_MBCHC, H, U, // debug, will go away
|
|
||||||
|
|
||||||
NEXT_MESSAGE: 1,
|
NEXT_MESSAGE: 1,
|
||||||
LOG_MESSAGES: false,
|
LOG_MESSAGES: false,
|
||||||
LOADED: false,
|
LOADED: false,
|
||||||
AUTOHACK_ENABLED: false,
|
AUTOHACK_ENABLED: false,
|
||||||
/**@type {number | undefined}*/LAST_HACKED: $,
|
/**@type {number | undefined}*/LAST_HACKED: $,
|
||||||
//HISTORY_MODE: false,
|
|
||||||
RE_PREF_ACTIVITY_ME: /^@/,
|
RE_PREF_ACTIVITY_ME: /^@/,
|
||||||
RE_PREF_ACTIVITY: /^@@/,
|
RE_PREF_ACTIVITY: /^@@/,
|
||||||
RE_ACT_CIDS: /^<(\d+)?:(\d+)?>/,
|
RE_ACT_CIDS: /^<(\d+)?:(\d+)?>/,
|
||||||
RE_LAST_WORD: /(^|\s)(\S*)$/,
|
RE_LAST_WORD: /(^|\s)(\S*)$/,
|
||||||
RE_LAST_LETTER: /\w$/,
|
RE_LAST_LETTER: /\w$/,
|
||||||
RE_ACTIVITY: new RegExp(`^${CommandsKey}activity `),
|
RE_ACTIVITY: new RegExp(`^${CommandsKey}activity `),
|
||||||
//PREF_ACTIVITY: `${CommandsKey}activity `,
|
|
||||||
UTC_OFFSET: new Date().getTimezoneOffset() * 60 * 1000,
|
UTC_OFFSET: new Date().getTimezoneOffset() * 60 * 1000,
|
||||||
MAP_ACTIONS: { // ActivityFemale3DCG
|
MAP_ACTIONS: { // ActivityFemale3DCG
|
||||||
// Action
|
// Action
|
||||||
|
@ -445,127 +448,6 @@ const/**@type {SDK.Hook}*/after = (name, f) => mod.hookFunction(name, 0, (na, n)
|
||||||
mbchc.run_activity(char, /**@type {AssetGroupItemName}*/(ag), /**@type {ActivityName}*/(action))
|
mbchc.run_activity(char, /**@type {AssetGroupItemName}*/(ag), /**@type {ActivityName}*/(action))
|
||||||
} catch (x) { U.report(x) }
|
} catch (x) { U.report(x) }
|
||||||
},
|
},
|
||||||
//complete(options, space = true) {
|
|
||||||
// if (m_t(options)) return void U.bell()
|
|
||||||
// if (options.length > 1) {
|
|
||||||
// const width = Math.max(...options.map(o => o.length))
|
|
||||||
// let pref = null
|
|
||||||
// for (let i = width; i > 0; i -= 1) {
|
|
||||||
// const test = options[0].slice(0, i)
|
|
||||||
// if (options.every(o => o.startsWith(test))) {
|
|
||||||
// pref = test
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (pref) this.complete([pref], false)
|
|
||||||
// this.comp_hint(options)
|
|
||||||
// } else W.ElementValue('InputChat', W.ElementValue('InputChat').replace(this.RE_LAST_WORD, `$1${options[0]}${space ? ' ' : $S}`))
|
|
||||||
//},
|
|
||||||
|
|
||||||
///**
|
|
||||||
// * Displays strings as completion hint
|
|
||||||
// * @param {string[]} options List of words to display. The order will be modified without copy.
|
|
||||||
// * @returns {undefined}
|
|
||||||
// */
|
|
||||||
//comp_hint(options) {
|
|
||||||
// if (m_t(options)) return
|
|
||||||
// this.COMP_HINT.innerHTML = '<div>' + options.sort().reverse().map(s => `<div>${s}</div>`).join($S) + '</div>'
|
|
||||||
// this.COMP_HINT.style.display = 'block'
|
|
||||||
// W.ElementSetDataAttribute(this.COMP_HINT.id, 'colortheme', (W.Player.ChatSettings?.ColorTheme || 'Light'))
|
|
||||||
// const rescroll = W.ElementIsScrolledToEnd('TextAreaChatLog')
|
|
||||||
// W.ChatRoomResize(false)
|
|
||||||
// if (rescroll) W.ElementScrollToEnd('TextAreaChatLog')
|
|
||||||
// this.COMP_HINT.firstChild?.lastChild?.scrollIntoView({behaviour: 'instant'})
|
|
||||||
//},
|
|
||||||
|
|
||||||
///**
|
|
||||||
// * Returns true if the completion box is attached to body and its display isn't none
|
|
||||||
// * @returns {boolean}
|
|
||||||
// */
|
|
||||||
//comp_hint_visible() {
|
|
||||||
// return this.COMP_HINT.parentElement && this.COMP_HINT.style.display !== 'none'
|
|
||||||
//},
|
|
||||||
//comp_hint_hide() {
|
|
||||||
// if (!this.comp_hint_visible()) return
|
|
||||||
// this.COMP_HINT.style.display = 'none'
|
|
||||||
// W.ChatRoomResize(false)
|
|
||||||
//},
|
|
||||||
//char2targets(/**@type {Character}*/char) {
|
|
||||||
// const/**@type {Set<string>}*/result = new Set()
|
|
||||||
// val(U.cid(char)?.toString(), id => result.add(id).add(`=${id}`))
|
|
||||||
// for (const t of U.split(char.Name)) {
|
|
||||||
// result.add(t)
|
|
||||||
// result.add(`@${t}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (char.Nickname !== $) for (const t of U.split(char.Nickname)) {
|
|
||||||
// result.add(t)
|
|
||||||
// result.add(`@${t}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return result
|
|
||||||
//},
|
|
||||||
//complete_target(/**@type {string}*/token, me2 = true, check_perms = false) {
|
|
||||||
// const [locase, found] = [token.toLocaleLowerCase(), new Set()]
|
|
||||||
// for (const c of U.crc) {
|
|
||||||
// if ((c.IsPlayer() && !me2) || (check_perms && !W.ServerChatRoomGetAllowItem(W.Player, c))) continue
|
|
||||||
// for (const s of this.char2targets(c)) {
|
|
||||||
// if (s.toLocaleLowerCase().startsWith(locase)) found.add(s)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //this.complete(Array.from(found))
|
|
||||||
//},
|
|
||||||
//complete_common() { // w.ElementValue('InputChat') will strip the trailing whitespace
|
|
||||||
// const E = D.querySelector('#InputChat')
|
|
||||||
// ass('somehow #InputChat is broken', E instanceof HTMLTextAreaElement)
|
|
||||||
// return [this, E.value, U.split(E.value)]
|
|
||||||
//},
|
|
||||||
//complete_mbchc(_args, _locase, _cmdline) {
|
|
||||||
// const [mbchc, _input, tokens] = W.MBCHC.complete_common(); // `this` is command object
|
|
||||||
// if (m_t(tokens)) return
|
|
||||||
// if (tokens.length < 2) return //mbchc.complete([`${CommandsKey}${this.Tag}`])
|
|
||||||
// const subname = tokens[1].toLocaleLowerCase()
|
|
||||||
// if (tokens.length < 3) return //mbchc.complete(Object.keys(SUBCOMMANDS_MBCHC).filter(c => c.startsWith(subname))) // Complete subcommand name
|
|
||||||
// const sub = SUBCOMMANDS_MBCHC[subname]
|
|
||||||
// if (sub && sub.args) {
|
|
||||||
// const argname = Object.keys(sub.args)[tokens.length - 3]
|
|
||||||
// if (argname === 'TARGET') return mbchc.complete_target(tokens.at(-1), false)
|
|
||||||
// if (argname === '[TARGET]') return mbchc.complete_target(tokens.at(-1))
|
|
||||||
// }
|
|
||||||
//},
|
|
||||||
//complete_fbc_anim(_args, _locase, _cmdline) {
|
|
||||||
// const [mbchc, _input, tokens] = W.MBCHC.complete_common(); // `this` is command object
|
|
||||||
// if (m_t(tokens)) return
|
|
||||||
// if (tokens.length < 2) return mbchc.complete([`${CommandsKey}${this.Tag}`])
|
|
||||||
// if (tokens.length > 2) return void U.bell()
|
|
||||||
// const anim = tokens[1].toLocaleLowerCase()
|
|
||||||
// return mbchc.complete(Object.keys(W.bce_EventExpressions).filter(a => a.toLocaleLowerCase().startsWith(anim)))
|
|
||||||
//},
|
|
||||||
//complete_fbc_pose(_args, _locase, _cmdline) {
|
|
||||||
// const [mbchc, _input, tokens] = W.MBCHC.complete_common(); // `this` is command object
|
|
||||||
// if (m_t(tokens)) return
|
|
||||||
// if (tokens.length < 2) return mbchc.complete([`${CommandsKey}${this.Tag}`])
|
|
||||||
// const pose = tokens.at(-1).toLocaleLowerCase()
|
|
||||||
// return mbchc.complete(W.PoseFemale3DCG.map(p => p.Name).filter(p => p.toLocaleLowerCase().startsWith(pose)))
|
|
||||||
//},
|
|
||||||
//focus_chat_checks() { // we only want to catch chat log and canvas (no map though) keypresses
|
|
||||||
// if (D.activeElement === D.body) return true
|
|
||||||
// if (D.activeElement?.id !== 'MainCanvas') return false
|
|
||||||
// return !W.ChatRoomMapViewIsActive()
|
|
||||||
//},
|
|
||||||
//focus_chat_whitelist(/**@type {KeyboardEvent}*/event) {
|
|
||||||
// if (event.ctrlKey && event.key === 'v') return true // Ctrl+V should paste
|
|
||||||
// return false
|
|
||||||
//},
|
|
||||||
//focus_chat(/**@type {KeyboardEvent}*/event) {
|
|
||||||
// if (event.repeat) return // Only unique presses please
|
|
||||||
// if (!this.focus_chat_checks()) return
|
|
||||||
// if ([event.altKey, event.ctrlKey, event.metaKey].some(Boolean) && !this.focus_chat_whitelist(event)) return // Alt, ctrl and meta should all be false
|
|
||||||
// if (U.style('#InputChat', s => s.display) !== 'inline') return // Input chat missing
|
|
||||||
// W.ElementFocus('InputChat')
|
|
||||||
//},
|
|
||||||
loader() {
|
loader() {
|
||||||
U.remove_loader_hook === $ || yes(void U.remove_loader_hook(), U.remove_loader_hook = $)
|
U.remove_loader_hook === $ || yes(void U.remove_loader_hook(), U.remove_loader_hook = $)
|
||||||
if (this.LOADED) return
|
if (this.LOADED) return
|
||||||
|
@ -577,18 +459,22 @@ const/**@type {SDK.Hook}*/after = (name, f) => mod.hookFunction(name, 0, (na, n)
|
||||||
{Tag: 'activity', Description: '[Message]: Send a custom activity (or "@@Message", or "@Message" as yourself)', Action: this.command_activity},
|
{Tag: 'activity', Description: '[Message]: Send a custom activity (or "@@Message", or "@Message" as yourself)', Action: this.command_activity},
|
||||||
{Tag: 'do', Description: ': Do an activity, as if clicked on its button ("/do" for help)', Action: this.command_do, AutoComplete: U.complete_do},
|
{Tag: 'do', Description: ': Do an activity, as if clicked on its button ("/do" for help)', Action: this.command_do, AutoComplete: U.complete_do},
|
||||||
]
|
]
|
||||||
mut(D.createElement('style'), c => void D.head.append(c), c => c.textContent = `
|
const/**@type (e: Event) => void*/css_hook = e => void cur(/**@type {HTMLStyleElement}*/(e.target), css => U.scroll() && void css.removeEventListener('load', css_hook))
|
||||||
|
mut(D.createElement('style'), c => void D.head.append(c), c => void c.addEventListener('load', css_hook), c => c.textContent = `
|
||||||
#TextAreaChatLog .mbchc { background-color: ${U.RGB.Polly}; margin-left: -0.4em; padding-left: 0.4em; }
|
#TextAreaChatLog .mbchc { background-color: ${U.RGB.Polly}; margin-left: -0.4em; padding-left: 0.4em; }
|
||||||
#TextAreaChatLog[data-colortheme^="dark"] .mbchc { background-color: ${U.RGB.Mute}; }
|
#TextAreaChatLog[data-colortheme^="dark"] .mbchc { background-color: ${U.RGB.Mute}; }
|
||||||
#${C.e.id} { display: none; text-align: right; }
|
#${C.e.id} { display: none; text-align: right; }
|
||||||
#${C.e.id} > div { overflow: auto; position: absolute; bottom: 0; right: 0; max-height: 100%; padding: 0 0.5ex; background-color: ${U.RGB.Polly}; color: black; }
|
#${C.e.id} > div { overflow: auto; position: absolute; bottom: 0; right: 0; max-height: 100%; padding: 0 0.5ex; background-color: ${U.RGB.Polly}; color: black; }
|
||||||
#${C.e.id}[data-colortheme^="dark"] > div { background-color: ${U.RGB.Mute}; color: white; }
|
#${C.e.id}[data-colortheme^="dark"] > div { background-color: ${U.RGB.Mute}; color: white; }
|
||||||
#${C.e.id} > div div { margin: 0.25ex 0; }
|
#${C.e.id} > div div { margin: 0.25ex 0; }
|
||||||
#chat-room-div #TextAreaChatLog::before { content: ''; display: block; height: 100%; }
|
#chat-room-div #TextAreaChatLog::before { content: ''; display: block; height: 100%; background: repeating-linear-gradient(135deg, transparent 0 20px, #333 2px 22px); }
|
||||||
#chat-room-div[data-mbchc-mode="h"] #TextAreaChatLog::after { content: '𝗵𝗶𝘀𝘁𝗼𝗿𝘆 ⟨𝘗𝘨𝘜𝘱/𝘋𝘯⟩ 𝗌𝖼𝗋𝗈𝗅𝗅 ⇅ ⟨𝘌𝘯𝘵𝘦𝘳⟩ 𝗌𝖾𝗇𝖽 ↵ ⟨𝘛𝘢𝘣⟩ 𝖾𝖽𝗂𝗍 ⌨ ⟨𝘌𝘴𝘤⟩ 𝖺𝖻𝗈𝗋𝗍 ⟲'; display: block; position: sticky; bottom: 0; background: black; color: orange; padding: 0 0.2ex; animation: 0.2s cubic-bezier(0.19, 1, 0.22, 1) mbchc_hist_hint_show; }
|
#chat-room-div[data-mbchc-mode="h"] #TextAreaChatLog::after {
|
||||||
|
content: '⟨𝘗𝘨𝘜𝘱/𝘋𝘯/↕⟩ 𝗌𝖼𝗋𝗈𝗅𝗅 ⇅ ⟨𝘌𝘯𝘵𝘦𝘳⟩ 𝗌𝖾𝗇𝖽 ↵ ⟨𝘛𝘢𝘣/↔/⌫⟩ 𝖾𝖽𝗂𝗍 ⌨ ⟨𝘌𝘴𝘤⟩ 𝖺𝖻𝗈𝗋𝗍 ⟲\\A' attr(data-mbchc-h-h); whitespace: pre;
|
||||||
|
display: block; position: sticky; z-index: 1; bottom: 0; background: black; color: orange; padding: 0 0.2ex; animation: 0.2s cubic-bezier(0.19, 1, 0.22, 1) mbchc_hh_show;
|
||||||
|
}
|
||||||
#InputChat:read-only { background: black; color: orange; }
|
#InputChat:read-only { background: black; color: orange; }
|
||||||
@keyframes mbchc_hist_hint_show { from {transform: translateX(-100%);} to {transform: translateX(0);} }
|
@keyframes mbchc_hh_show { from {transform: translateX(-100%);} to {transform: translateX(0);} }
|
||||||
`)
|
`) // will always scroll the chat on CSS load, I can't be fucked to make it conditional
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
this.calculate_maps()
|
this.calculate_maps()
|
||||||
|
@ -603,14 +489,6 @@ const/**@type {SDK.Hook}*/after = (name, f) => mod.hookFunction(name, 0, (na, n)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
prior('ChatRoomSendChat', () => val(U.ic, ic => !ic.value.startsWith('@@@') && ic.value.startsWith('@') && (ic.value = ic.value.replace(U.RE['@'][1], U.ACT).replace(U.RE['@'][0], U.replace_me))))
|
prior('ChatRoomSendChat', () => val(U.ic, ic => !ic.value.startsWith('@@@') && ic.value.startsWith('@') && (ic.value = ic.value.replace(U.RE['@'][1], U.ACT).replace(U.RE['@'][0], U.replace_me))))
|
||||||
//{
|
|
||||||
// let input = W.ElementValue('InputChat')
|
|
||||||
// if (!input.startsWith('@@@') && input.startsWith('@')) {
|
|
||||||
// input = input.replace(this.RE_PREF_ACTIVITY, this.PREF_ACTIVITY)
|
|
||||||
// input = input.replace(this.RE_PREF_ACTIVITY_ME, this.replace_me)
|
|
||||||
// W.ElementValue('InputChat', input)
|
|
||||||
// }
|
|
||||||
//})
|
|
||||||
after('ChatRoomSendChat', () => { // FIXME actually make history a ring buffer of a given size. clear the array and push every string into it, compacting sequential equal strings into one.
|
after('ChatRoomSendChat', () => { // FIXME actually make history a ring buffer of a given size. clear the array and push every string into it, compacting sequential equal strings into one.
|
||||||
const history = W.ChatRoomLastMessage
|
const history = W.ChatRoomLastMessage
|
||||||
if ((history.length > 1) && (history.at(-1) === history.at(-2))) {
|
if ((history.length > 1) && (history.at(-1) === history.at(-2))) {
|
||||||
|
@ -629,45 +507,34 @@ const/**@type {SDK.Hook}*/after = (name, f) => mod.hookFunction(name, 0, (na, n)
|
||||||
const/**@type {(this: HTMLTextAreaElement, e: KeyboardEvent) => void}*/ickd = function(e) {
|
const/**@type {(this: HTMLTextAreaElement, e: KeyboardEvent) => void}*/ickd = function(e) {
|
||||||
C.hide()
|
C.hide()
|
||||||
const ic = this // eslint-disable-line unicorn/no-this-assignment,@typescript-eslint/no-this-alias
|
const ic = this // eslint-disable-line unicorn/no-this-assignment,@typescript-eslint/no-this-alias
|
||||||
if (ic.readOnly) switch (e.key) {
|
if (ic.readOnly && !e.repeat) switch (e.key) { // FIXME maybe deal with modifiers
|
||||||
case 'Tab': case 'Escape':
|
case 'ArrowUp': case 'ArrowDown': W.ChatRoomScrollHistory(e.key === 'ArrowUp'); break
|
||||||
|
case 'Escape': case 'Tab': case 'ArrowRight': case 'ArrowLeft':
|
||||||
e.stopImmediatePropagation()
|
e.stopImmediatePropagation()
|
||||||
e.preventDefault() // falls through
|
e.preventDefault() // falls through
|
||||||
case 'Enter':
|
case 'Enter': case 'Backspace': H.exit(ic, e) // no default
|
||||||
H.exit(ic, e.key === 'Escape') // no default
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
after('ChatRoomCreateElement', () => { // This thing runs on every frame actually.
|
after('ChatRoomCreateElement', () => { // This thing runs on every frame actually.
|
||||||
C.e.parentElement ?? D.body.append(C.e)
|
C.e.parentElement ?? D.body.append(C.e)
|
||||||
val(U.ic, ic => ic.dataset['mbchc'] ?? void ic.addEventListener('keydown', ickd) ?? (ic.dataset['mbchc'] = 'yes'))
|
val(U.ic, ic => ic.dataset['mbchc'] ?? void ic.addEventListener('keydown', ickd) ?? (ic.dataset['mbchc'] = 'yes'))
|
||||||
//val(asa(HTMLDivElement, D.querySelector('#TextAreaChatLog')), c => c.scrollHeight > c.clientHeight || void U.pad_chat(c))
|
|
||||||
})
|
})
|
||||||
prior('ChatRoomClearAllElements', () => C.hide() && void C.e.remove())
|
prior('ChatRoomClearAllElements', () => C.hide() && void C.e.remove())
|
||||||
D.addEventListener('click', _ => C.hide()) // downstream handlers can capture clicks, but I can't be fucked to be honest
|
D.addEventListener('click', _ => C.hide()) // downstream handlers can capture clicks, but I can't be fucked to be honest
|
||||||
//after('ChatRoomResize', () => {
|
|
||||||
// if (W.CharacterGetCurrent() === null && W.CurrentScreen === 'ChatRoom' && D.querySelector('#InputChat') && D.querySelector('#TextAreaChatLog') && this.comp_hint_visible()) { // Upstream
|
|
||||||
// const fontsize = ChatRoomFontSize
|
|
||||||
// //w.ElementPositionFix('TextAreaChatLog', fontsize, 1005, 66, 988, 630)
|
|
||||||
// //w.ElementPositionFix(this.COMP_HINT.id, fontsize, 1005, 701, 988, 200)
|
|
||||||
// W.ElementPositionFix(this.COMP_HINT.id, fontsize, 800, 65, 200, 835)
|
|
||||||
// //this.COMP_HINT.style.display = 'flex'
|
|
||||||
// }
|
|
||||||
//})
|
|
||||||
after('ChatRoomResize', () => {
|
after('ChatRoomResize', () => {
|
||||||
if (W.CharacterGetCurrent() === null && W.CurrentScreen === 'ChatRoom' && U.ic !== $ && D.querySelector('#TextAreaChatLog') !== null && !C.hidden) { // Upstream
|
if (W.CharacterGetCurrent() === null && W.CurrentScreen === 'ChatRoom' && U.ic !== $ && D.querySelector('#TextAreaChatLog') !== null && !C.hidden) { // Upstream
|
||||||
W.ElementPositionFix(C.e.id, ChatRoomFontSize, 800, 65, 200, 835)
|
W.ElementPositionFix(C.e.id, ChatRoomFontSize, 800, 65, 200, 835)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
//D.addEventListener('keydown', event => void this.focus_chat(event)) // Looks like the club got better at this
|
|
||||||
|
|
||||||
mod.hookFunction('ChatRoomScrollHistory', 0, ([up]) => void val(U.ic, ic => { const input = ic.value, history = W.ChatRoomLastMessage
|
mod.hookFunction('ChatRoomScrollHistory', 0, ([up]) => void val(U.ic, ic => { const input = ic.value, history = W.ChatRoomLastMessage
|
||||||
// FIXME we'll make it better when the history is a proper ring buffer
|
// FIXME we'll make it better when the history is a proper ring buffer
|
||||||
if (!ic.readOnly) {
|
if (!ic.readOnly) {
|
||||||
const/**@type {Map<string,number>}*/map = new Map()
|
const/**@type {Map<string,number>}*/map = new Map()
|
||||||
const/**@type {(l: string, i: number, I: string) => boolean}*/ cond = m_t(input) ? (_, i, __) => i > 0 : (l, _, i) => l !== i && l.startsWith(i)
|
const/**@type {(l: string, i: number, I: string) => boolean}*/ cond = m_t(input) ? (_, i, __) => i > 0 : (l, _, i) => l !== i && l.startsWith(i)
|
||||||
if (m_t(history.reduce((ax, l, i) => cond(l, i, input) ? ax.set(l, i) : ax, map))) return U.bell()
|
if (m_t(history.reduce((ax, l, i) => cond(l, i, input) ? ax.set(l, i) : ax, map))) return U.bell()
|
||||||
H.enter(ic, input, W.ElementIsScrolledToEnd('TextAreaChatLog'), new Set(map.values()))
|
val(asa(HTMLDivElement, D.querySelector('#TextAreaChatLog')), t => t.dataset['mbchcHH'] = `𝗵𝗶𝘀𝘁𝗼𝗿𝘆: ${m_t(input) ? 'Everything' : `Prefix: ${input}`}`)
|
||||||
|
H.enter(ic, input, U.scrolled, new Set(map.values()))
|
||||||
}
|
}
|
||||||
if (ic.readOnly) { // this can't be an else, because we mutate state above. To be honest, this will always be true, but I want to make sure.
|
if (ic.readOnly) { // this can't be an else, because we mutate state above. To be honest, this will always be true, but I want to make sure.
|
||||||
if (H.ids === $) return U.bell() // shouldn't happen?
|
if (H.ids === $) return U.bell() // shouldn't happen?
|
||||||
|
|
12
package-lock.json
generated
12
package-lock.json
generated
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "mbchc",
|
"name": "mbchc",
|
||||||
"version": "107.13.0",
|
"version": "108.13.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mbchc",
|
"name": "mbchc",
|
||||||
"version": "107.13.0",
|
"version": "108.13.1",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bc-stubs": "^107.0.0",
|
"bc-stubs": "^108.0.0",
|
||||||
"bondage-club-mod-sdk": "^1.2.0",
|
"bondage-club-mod-sdk": "^1.2.0",
|
||||||
"typescript": "^5.5.2",
|
"typescript": "^5.5.2",
|
||||||
"xo": "^0.56.0"
|
"xo": "^0.56.0"
|
||||||
|
@ -1074,9 +1074,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/bc-stubs": {
|
"node_modules/bc-stubs": {
|
||||||
"version": "107.0.0",
|
"version": "108.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/bc-stubs/-/bc-stubs-107.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bc-stubs/-/bc-stubs-108.0.0.tgz",
|
||||||
"integrity": "sha512-PUEvMGe7dDm+lKy6YJIWv1xWCOBVt/WAn4xrG8mBUoGobZVBqOrg+x5lOV0HvG9fcBB8K5NEaXMciE5TOTWkBA==",
|
"integrity": "sha512-b3ko8zl9zn5umYKC7uMQjEtOQxcbAdalUvJ7DoOPyJX4WcsdJFq+R76Rr8bhTtcRq7aW3tk7DtDjTy9awd7CRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"socket.io-client": "4.6.1"
|
"socket.io-client": "4.6.1"
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "mbchc",
|
"name": "mbchc",
|
||||||
"version": "107.13.0",
|
"version": "108.13.1",
|
||||||
"description": "Mute's Bondage Club Hacks Collection",
|
"description": "Mute's Bondage Club Hacks Collection",
|
||||||
"author": "Mute",
|
"author": "Mute",
|
||||||
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bc-stubs": "^107.0.0",
|
"bc-stubs": "^108.0.0",
|
||||||
"bondage-club-mod-sdk": "^1.2.0",
|
"bondage-club-mod-sdk": "^1.2.0",
|
||||||
"typescript": "^5.5.2",
|
"typescript": "^5.5.2",
|
||||||
"xo": "^0.56.0"
|
"xo": "^0.56.0"
|
||||||
|
@ -46,6 +47,7 @@
|
||||||
"brace-style": "off",
|
"brace-style": "off",
|
||||||
"camelcase": "off",
|
"camelcase": "off",
|
||||||
"capitalized-comments": "off",
|
"capitalized-comments": "off",
|
||||||
|
"complexity": "off",
|
||||||
"curly": "off",
|
"curly": "off",
|
||||||
"generator-star-spacing": "off",
|
"generator-star-spacing": "off",
|
||||||
"max-nested-callbacks": "off",
|
"max-nested-callbacks": "off",
|
||||||
|
|
|
@ -24,7 +24,8 @@ const h_all = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = createServer((rq, rx) => {
|
const server = createServer((rq, rx) => {
|
||||||
rq.method !== undefined && resp[rq.method] !== undefined && (new URL(`http://${config.host}:${config.port}${rq.url}`)).pathname === '/' ? resp[rq.method](rx) : rx.writeHead(400)
|
const handler = resp[rq.method ?? '']
|
||||||
|
handler !== undefined && (new URL(`http://${config.host}:${config.port}${rq.url}`)).pathname === '/' ? handler(rx) : rx.writeHead(400)
|
||||||
rx.end(() => void console.log('%s %d %s %s', (new Date()).toISOString(), rx.statusCode, rq.method, rq.url))
|
rx.end(() => void console.log('%s %d %s %s', (new Date()).toISOString(), rx.statusCode, rq.method, rq.url))
|
||||||
})
|
})
|
||||||
server.listen(config.port, config.host, () => void console.log(`Server started at http://${config.host}:${config.port} for ${config.filename}`))
|
server.listen(config.port, config.host, () => void console.log(`Server started at http://${config.host}:${config.port} for ${config.filename}`))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user