107.13.1 scroll on css (works now), also made spacer visible

This commit is contained in:
Mute 2024-09-08 15:31:33 +00:00
parent b9b1226e60
commit 9a646716bf
4 changed files with 9 additions and 155 deletions

3
ambient.d.ts vendored
View File

@ -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

154
mbchc.mjs
View File

@ -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 = '107.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
@ -229,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
@ -452,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 = U.scrolled
// W.ChatRoomResize(false)
// if (rescroll) U.scroll()
// 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
@ -584,21 +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},
] ]
U.rescroll(_ => 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 { #chat-room-div[data-mbchc-mode="h"] #TextAreaChatLog::after {
content: '⟨𝘗𝘨𝘜𝘱/𝘋𝘯/↕⟩ 𝗌𝖼𝗋𝗈𝗅𝗅 ⇅ ⟨𝘌𝘯𝘵𝘦𝘳⟩ 𝗌𝖾𝗇𝖽 ↵ ⟨𝘛𝘢𝘣/↔/⌫⟩ 𝖾𝖽𝗂𝗍 ⌨ ⟨𝘌𝘴𝘤⟩ 𝖺𝖻𝗈𝗋𝗍 ⟲\\A' attr(data-mbchc-h-h); whitespace: pre; 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; 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_hh_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()
@ -613,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))) {
@ -650,27 +518,15 @@ const/**@type {SDK.Hook}*/after = (name, f) => mod.hookFunction(name, 0, (na, n)
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) {

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "mbchc", "name": "mbchc",
"version": "107.13.0", "version": "107.13.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mbchc", "name": "mbchc",
"version": "107.13.0", "version": "107.13.1",
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"devDependencies": { "devDependencies": {
"bc-stubs": "^107.0.0", "bc-stubs": "^107.0.0",

View File

@ -1,8 +1,9 @@
{ {
"name": "mbchc", "name": "mbchc",
"version": "107.13.0", "version": "107.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": "^107.0.0",