diff --git a/mbchc.mjs b/mbchc.mjs index 0d99b39..1e0c0e1 100644 --- a/mbchc.mjs +++ b/mbchc.mjs @@ -1,10 +1,11 @@ export {} -if (!window.AsylumGGTSSAddItems) throw new Error('AsylumGGTSSAddItems() not found, aborting MBCHC loading') +// Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsy value +const MISSING_PLAYER_DIALOG = {Tag: 'MISSING PLAYER DIALOG: ', Text: '\u200C'} + if (window.MBCHC) throw new Error('MBCHC found, aborting loading') window.MBCHC = { VERSION: 'dev.9', - TARGET_VERSION: 'R99', NEXT_MESSAGE: 1, LOG_MESSAGES: false, RETHROW: false, @@ -12,7 +13,7 @@ window.MBCHC = { AUTOHACK_ENABLED: false, LAST_HACKED: null, HISTORY_MODE: false, - RE_TITLE: /^[a-zA-Z]+$/, + // RE_TITLE: /^[a-zA-Z]+$/, RE_PREF_ACTIVITY_ME: /^@/, RE_PREF_ACTIVITY: /^@@/, RE_ACT_CIDS: /^<(\d+)?:(\d+)?>/, @@ -25,62 +26,62 @@ window.MBCHC = { RGB_MUTE: '#6c2132', RGB_POLLY: '#81b1e7', UTC_OFFSET: new Date().getTimezoneOffset() * 60 * 1000, - HIDE_SPECIAL: ['Activity', 'Emoticon'], - HIDE_BODY: ['Blush', 'BodyLower', 'BodyUpper', 'Eyebrows', 'Eyes', 'Eyes2', 'Face', 'Fluids', 'HairBack', 'HairFront', 'Hands', 'Head', 'LeftHand', 'Mouth', 'Nipples', 'Pussy', 'RightHand'], - HIDE_CLOTHES: [ - 'Cloth', - 'ClothAccessory', - 'Necklace', - 'Suit', - 'ClothLower', - 'SuitLower', - 'Bra', - 'Corset', - 'Panties', - 'Socks', - 'RightAnklet', - 'LeftAnklet', - 'Garters', - 'Shoes', - 'Hat', - 'HairAccessory3', - 'HairAccessory1', - 'HairAccessory2', - 'Gloves', - 'Bracelet', - 'Glasses', - 'Mask', - 'TailStraps', - 'Wings', - ], - HIDE_ITEMS: [ - 'ItemMisc', - 'ItemEars', - 'ItemHead', - 'ItemNose', - 'ItemHood', - 'ItemAddon', - 'ItemMouth', - 'ItemMouth2', - 'ItemMouth3', - 'ItemArms', - 'ItemNeckAccessories', - 'ItemNeck', - 'ItemNeckRestraints', - 'ItemNipples', - 'ItemNipplesPiercings', - 'ItemBreast', - 'ItemTorso', - 'ItemTorso2', - 'ItemHands', - 'ItemPelvis', - 'ItemVulva', - 'ItemVulvaPiercings', - 'ItemDevices', - 'ItemLegs', - 'ItemFeet', - 'ItemBoots', - ], + // HIDE_SPECIAL: ['Activity', 'Emoticon'], + // HIDE_BODY: ['Blush', 'BodyLower', 'BodyUpper', 'Eyebrows', 'Eyes', 'Eyes2', 'Face', 'Fluids', 'HairBack', 'HairFront', 'Hands', 'Head', 'LeftHand', 'Mouth', 'Nipples', 'Pussy', 'RightHand'], + // HIDE_CLOTHES: [ + // 'Cloth', + // 'ClothAccessory', + // 'Necklace', + // 'Suit', + // 'ClothLower', + // 'SuitLower', + // 'Bra', + // 'Corset', + // 'Panties', + // 'Socks', + // 'RightAnklet', + // 'LeftAnklet', + // 'Garters', + // 'Shoes', + // 'Hat', + // 'HairAccessory3', + // 'HairAccessory1', + // 'HairAccessory2', + // 'Gloves', + // 'Bracelet', + // 'Glasses', + // 'Mask', + // 'TailStraps', + // 'Wings', + // ], + // HIDE_ITEMS: [ + // 'ItemMisc', + // 'ItemEars', + // 'ItemHead', + // 'ItemNose', + // 'ItemHood', + // 'ItemAddon', + // 'ItemMouth', + // 'ItemMouth2', + // 'ItemMouth3', + // 'ItemArms', + // 'ItemNeckAccessories', + // 'ItemNeck', + // 'ItemNeckRestraints', + // 'ItemNipples', + // 'ItemNipplesPiercings', + // 'ItemBreast', + // 'ItemTorso', + // 'ItemTorso2', + // 'ItemHands', + // 'ItemPelvis', + // 'ItemVulva', + // 'ItemVulvaPiercings', + // 'ItemDevices', + // 'ItemLegs', + // 'ItemFeet', + // 'ItemBoots', + // ], MAP_ACTIONS: { // ActivityFemale3DCG // action 'nod|yes': {Head: {self: 'Nod'}}, @@ -182,11 +183,11 @@ window.MBCHC = { [/([^\\])\$/g, '$1\\.?$$'], ], SUBCOMMANDS_MBCHC: { - versions: {desc: 'show the mod versions across the room', cb: mbchc => mbchc.inform(mbchc.gather_versions().map(c => `
${c.name} (${c.cid}): ${c.version}
`).join(''))}, + // versions: {desc: 'show the mod versions across the room', cb: mbchc => mbchc.inform(mbchc.gather_versions().map(c => `
${c.name} (${c.cid}): ${c.version}
`).join(''))}, autohack: {desc: 'toggle the autohack feature', cb: mbchc => mbchc.inform(`Autohack is now ${(mbchc.AUTOHACK_ENABLED = !mbchc.AUTOHACK_ENABLED) ? 'enabled' : 'disabled'}`)}, // eslint-disable-line no-cond-assign - disappear: {desc: 'become invisible (requires anal hook -> hair)', cb: mbchc => mbchc.disappear()}, + // disappear: {desc: 'become invisible (requires anal hook -> hair)', cb: mbchc => mbchc.disappear()}, donate: {desc: 'Buy data and send it to recipient', args: {TARGET: {}}, cb: (mbchc, args) => mbchc.donate_data(args[0])}, - title: {desc: 'set a custom title (WIP)', args: {TITLE: {}}, cb: (mbchc, args) => mbchc.title(args[0])}, + // title: {desc: 'set a custom title (WIP)', args: {TITLE: {}}, cb: (mbchc, args) => mbchc.title(args[0])}, tz: {desc: 'set target\'s UTC offset', args: {OFFSET: {}, '[TARGET]': {}}, cb: (mbchc, args) => mbchc.set_timezone(args)}, 'purge!': {desc: 'delete MBCHC online saved data', cb(mbchc) { if (window.Player.OnlineSettings.MBCHC) { @@ -209,10 +210,8 @@ window.MBCHC = { const processed = {self: (actions.self) ? actions.self.split('|').concat(all) : all, others: (actions.others) ? actions.others.split('|').concat(all) : all} for (const zone of zones.split(',')) unwound[`Item${zone}`] = processed } - for (const verb of verbs.split('|')) this.DO_DATA.verbs[verb] = unwound } - for (const [ag, zones] of Object.entries(this.MAP_ZONES)) for (const zone of zones) this.DO_DATA.zones[zone] = ag }, settings(setting = null) { @@ -224,7 +223,6 @@ window.MBCHC = { if (!window.Player.OnlineSettings.MBCHC) window.Player.OnlineSettings.MBCHC = {} cb.call(this, window.Player.OnlineSettings.MBCHC) } - window.ServerAccountUpdate.QueueData({OnlineSettings: window.Player.OnlineSettings}) }, log(level, message) { @@ -244,7 +242,6 @@ window.MBCHC = { const rest = result.slice(1) result = first + rest } - if (options.dot && this.RE_LAST_LETTER.test(result)) result = `${result}.` return (result) }, @@ -331,7 +328,7 @@ window.MBCHC = { if (window.Player.Money < cost) throw new Error('not enough money') window.CharacterChangeMoney(window.Player, -cost) window.ServerSend('ChatRoomChat', {Content: 'ReceiveSuitcaseMoney', Type: 'Hidden', Target: char.cid}) - window.ChatRoomMessage({Sender: window.Player.cid, Type: 'Action', Content: `You've bought data for $${cost} and sent it to ${char.dn}.`, Dictionary: [{Tag: 'MISSING PLAYER DIALOG: ', Text: ''}]}) + window.ChatRoomMessage({Sender: window.Player.cid, Type: 'Action', Content: `You've bought data for $${cost} and sent it to ${char.dn}.`, Dictionary: [MISSING_PLAYER_DIALOG]}) }, run_activity(char, ag, action) { try { @@ -357,14 +354,13 @@ window.MBCHC = { return ({Tag: `${type}Character`, MemberNumber: cid, Text: this.cid2char(cid).dn}) }, send_activity(message) { - const dict = [{Tag: 'MISSING PLAYER DIALOG: ', Text: '\u200C'}] // Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsy value + const dict = [MISSING_PLAYER_DIALOG] const cids = message.match(this.RE_ACT_CIDS) if (cids) { message = message.replace(this.RE_ACT_CIDS, '') if (cids[1]) dict.push(this.cid2dict('Source', cids[1])) if (cids[2]) dict.push(this.cid2dict('Target', cids[2]), this.cid2dict('Destination', cids[2])) } - window.ServerSend('ChatRoomChat', {Type: 'Action', Content: message, Dictionary: dict}) }, receive(data) { @@ -377,10 +373,8 @@ window.MBCHC = { if (payload.type === 'greetings') this.hello(char) break } - default: // If we don't know the type it may be from a newer version } - return true }, hello(char = null) { @@ -390,22 +384,22 @@ window.MBCHC = { if (char) message.Target = char.cid window.ServerSend('ChatRoomChat', message) }, - disappear() { - const item = window.InventoryGet(window.Player, 'ItemButt') - if (!item || !item.Asset || !item.Asset.Name) throw new Error('butt seems empty') - if (item.Asset.Name !== 'AnalHook') throw new Error('butt seems occupied by something other than the anal hook') - if (!item.Property.Type || item.Property.Type !== 'Hair') throw new Error('anal hook seems not tied to hair') - item.Property = {Type: 'Hair', Hide: this.HIDE_ALL} - window.CharacterRefresh(window.Player, true, true) - }, - title(title) { // WIP - if (this.empty(title)) throw new Error('empty title') - title = this.normalise_message(title, {trim: true, up: true, low: true}) - if (title.length > 16) throw new Error('title too long') - if (!this.RE_TITLE.test(title)) throw new Error('invalid title') - window.TitleSet(title) - // Window.TitleList.push({Name: title, Requirement: () => true}) // check for existing first - }, + // disappear() { + // const item = window.InventoryGet(window.Player, 'ItemButt') + // if (!item || !item.Asset || !item.Asset.Name) throw new Error('butt seems empty') + // if (item.Asset.Name !== 'AnalHook') throw new Error('butt seems occupied by something other than the anal hook') + // if (!item.Property.Type || item.Property.Type !== 'Hair') throw new Error('anal hook seems not tied to hair') + // item.Property = {Type: 'Hair', Hide: this.HIDE_ALL} + // window.CharacterRefresh(window.Player, true, true) + // }, + // title(title) { // WIP + // if (this.empty(title)) throw new Error('empty title') + // title = this.normalise_message(title, {trim: true, up: true, low: true}) + // if (title.length > 16) throw new Error('title too long') + // if (!this.RE_TITLE.test(title)) throw new Error('invalid title') + // window.TitleSet(title) + // // Window.TitleList.push({Name: title, Requirement: () => true}) // check for existing first + // }, copy_fbc_trigger(trigger) { const result = { Type: 'Action', @@ -418,15 +412,15 @@ window.MBCHC = { this.remove_fbc_hook() delete this.remove_fbc_hook window.bce_ActivityTriggers.push(...window.bce_ActivityTriggers.filter(t => t.Type === 'Emote').map(t => this.copy_fbc_trigger(t))) - /* (["anim", "pose"]).forEach(tag => {let cmd = window.Commands.find(c => tag === c.Tag); if (cmd) cmd.AutoComplete = this[`complete_fbc_${tag}`]}) */ // this line explodes, don't ask me why + /* (["anim", "pose"]).forEach(tag => {let cmd = window.Commands.find(c => tag === c.Tag); if (cmd) cmd.AutoComplete = this[`complete_fbc_${tag}`]}) */ // this line explodes, because it needs a semicolon in front if it let cmd = window.Commands.find(c => c.Tag === 'anim') if (cmd) cmd.AutoComplete = this.complete_fbc_anim cmd = window.Commands.find(c => c.Tag === 'pose') if (cmd) cmd.AutoComplete = this.complete_fbc_pose }, - gather_versions() { - return (window.ChatRoomCharacter.filter(c => c.MBCHC).map(c => ({name: c.dn, cid: c.cid, version: c.MBCHC.VERSION}))) - }, + // gather_versions() { + // return (window.ChatRoomCharacter.filter(c => c.MBCHC).map(c => ({name: c.dn, cid: c.cid, version: c.MBCHC.VERSION}))) + // }, find_timezone(char) { const timezones = this.settings('timezones') if (timezones && typeof timezones[char.cid] === 'number') return (timezones[char.cid]) @@ -629,15 +623,20 @@ window.MBCHC = { window.ElementValue('InputChat', history[found]) window.ChatRoomLastMessageIndex = found }, + focus_chat_checks() { // we only want to catch chatlog and canvas (no map though) keypresses + if (document.activeElement === document.body) return true + if (document.activeElement.id !== 'MainCanvas') return false + return !window.ChatRoomMapVisible + }, focus_chat_whitelist(event) { if (event.ctrlKey && event.key === 'v') return true // Ctrl+V should paste return false }, - focus_chat(event) { // TODO: this is not ideal, but it will have to do for now + focus_chat(event) { if (event.repeat) return // Only unique presses please - if (document.querySelector('#InputChat')?.style.display !== 'inline') return // Input chat missing - if (['InputChat', 'bce-message-input'].includes(document.activeElement.id)) return // Focus already set + 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 (document.querySelector('#InputChat')?.style.display !== 'inline') return // Input chat missing window.ElementFocus('InputChat') }, loader() { @@ -653,14 +652,13 @@ window.MBCHC = { {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: this.complete_do}, ] - this.HIDE_ALL = this.HIDE_SPECIAL.concat(this.HIDE_BODY).concat(this.HIDE_CLOTHES).concat(this.HIDE_ITEMS) + // this.HIDE_ALL = this.HIDE_SPECIAL.concat(this.HIDE_BODY).concat(this.HIDE_CLOTHES).concat(this.HIDE_ITEMS) this.CommandsKey = CommandsKey /* eslint-disable-line no-undef */ // window.CommandsKey is undefined this.RE_ACTIVITY = new RegExp(`^${this.CommandsKey}activity `) this.PREF_ACTIVITY = `${this.CommandsKey}activity ` this.COMP_HINT = document.createElement('div') this.COMP_HINT.id = 'mbchcCompHint' const css = document.createElement('style') - css.type = 'text/css' css.textContent = ` #TextAreaChatLog .mbchc, #TextAreaChatLog .mbchc { background-color: ${this.RGB_POLLY}; @@ -715,9 +713,9 @@ window.MBCHC = { } }) this.before('ChatRoomDrawCharacterOverlay', (C, CharX, CharY, Zoom, _Pos) => { - if (window.ChatRoomHideIconState < 1 && C.MBCHC) { - window.DrawRect(CharX + (175 * Zoom), CharY, 50 * Zoom, 50 * Zoom, C.MBCHC.VERSION === window.Player.MBCHC.VERSION ? this.RGB_POLLY : this.RGB_MUTE) - } + // if (window.ChatRoomHideIconState < 1 && C.MBCHC) { + // window.DrawRect(CharX + (175 * Zoom), CharY, 50 * Zoom, 50 * Zoom, C.MBCHC.VERSION === window.Player.MBCHC.VERSION ? this.RGB_POLLY : this.RGB_MUTE) + // } if (window.ChatRoomHideIconState < 1 && C.MBCHC_LOCAL && typeof C.MBCHC_LOCAL.TZ === 'number') { const hours = new Date(window.CommonTime() + this.UTC_OFFSET + (C.MBCHC_LOCAL.TZ * 60 * 60 * 1000)).getHours() window.DrawTextFit(hours < 10 ? '0' + hours.toString() : hours.toString(), CharX + (200 * Zoom), CharY + (25 * Zoom), 46 * Zoom, 'white', 'black') @@ -780,13 +778,14 @@ window.MBCHC = { // Footer this.LOADED = true this.log('info', `loaded version ${this.VERSION}`) - if (window.GameVersion !== this.TARGET_VERSION) this.log('warn', `Game version doesn't match the target ("${this.TARGET_VERSION}"), beware of incompatibilities`) // TODO: betas are like R86Beta1; cheat? if ((window.CurrentModule === 'Online') && (window.CurrentScreen === 'ChatRoom')) { for (const c of window.ChatRoomCharacter) this.update_char(c) this.player_enters_room() } }, preloader() { + if (!window.AsylumGGTSSAddItems) throw new Error('AsylumGGTSSAddItems() not found, aborting MBCHC loading') + if (!window.bcModSdk) throw new Error('SDK not found, please load with (or after) FUSAM') this.SDK = window.bcModSdk.registerMod({name: 'MBCHC', fullName: 'Mute\'s Bondage Club Hacks Collection', version: this.VERSION, repository: 'https://code.fleshless.org/mute/MBCHC/'}) this.before = (name, cb) => this.SDK.hookFunction(name, 0, (nextargs, next) => { try { @@ -811,3 +810,5 @@ window.MBCHC = { } window.MBCHC.preloader() + +// TODO: with the removal of /mbchc versions it makes no sense to keep track of the mod version and the whole hello() infrastructure can go away