diff --git a/mbchc.user.js b/mbchc.user.js index 13f6502..801c7d8 100644 --- a/mbchc.user.js +++ b/mbchc.user.js @@ -1 +1,250 @@ -// no stable release yet +// ==UserScript== +// @name MBCHC +// @version 1 +// @description Mute's Bondage Club Hacks Collection +// @author codename.mute@proton.me +// @namespace https://code.fleshless.org/mute/ +// @homepage https://code.fleshless.org/mute/MBCHC +// @updateURL https://code.fleshless.org/mute/MBCHC/raw/branch/master/mbchc.user.js +// @downloadURL https://code.fleshless.org/mute/MBCHC/raw/branch/master/mbchc.user.js +// @match https://bondageprojects.elementfx.com/R* +// @match https://www.bondageprojects.elementfx.com/R* +// @match https://bondage-europe.com/R* +// @match https://www.bondage-europe.com/R* +// @grant none +// ==/UserScript== + +(function() { + "use strict"; + if (!window.AsylumGGTSSAddItems) throw "AsylumGGTSSAddItems() not found, aborting" + + // Static data + window.MBCHC = { + NEXT_MESSAGE: 1, + LOG_MESSAGES: false, + LOADED: false, + VERSION: "1", + AUTOHACK_ENABLED: false, + LAST_HACKED: null, + RE_TITLE: /^[a-zA-Z]+$/, + HIDE_SPECIAL: ["Activity","Emoticon"], + HIDE_BODY: ["Blush","BodyLower","BodyUpper","Eyebrows","Eyes","Eyes2","Face","Fluids","HairBack","HairFront","Hands","Head","Mouth","Nipples","Pussy"], + 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", + "ItemHands","ItemPelvis","ItemVulva","ItemVulvaPiercings", + "ItemDevices","ItemLegs","ItemFeet","ItemBoots" + ], + COMMANDS: [ + { Tag: "disappear", + Description: ": Become invisible; requires anal hook (hair)", + Action: (argline, cmdline, args) => { + try { + window.MBCHC.make_my_anal_hook_hide_body() + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + }, + }, + { Tag: "title", + Description: "[Title]: (WIP) Set a custom title (one short word, letters only)", + Action: (argline, cmdline, args) => { + try { + window.MBCHC.action_title(args) + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + }, + }, + { Tag: "donate", + Description: "[MemberNumber]: Buy data and send it to recipient", + Action: (argline, cmdline, args) => { + try { + window.MBCHC.action_donate(args) + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + }, + }, + { Tag: "autohack", + Description: ": Toggle autohack mode", + Action: (argline, cmdline, args) => { + try { + window.MBCHC.action_autohack() + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + }, + }, + { Tag: "nod", + Description: ": Nod", + Action: (argline, cmdline, args) => { + try { + window.MBCHC.run_activity(window.Player, "ItemHead", "Nod") + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + }, + }, + { Tag: "shake", + Description: ": Shake your head", + Action: (argline, cmdline, args) => { + try { + window.MBCHC.run_activity(window.Player, "ItemHead", "Wiggle") + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + }, + }, + { Tag: "shrug", + Description: ": Shrug", + Action: (argline, cmdline, args) => { + try { + window.MBCHC.send_activity(`${window.CharacterNickname(window.Player)} shrugs.`) + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + }, + }, + { Tag: "myself", + Description: "[Message]: Send a custom activity as yourself (or \"@Message\")", + Action: (argline, cmdline, args) => { + if (!window.MBCHC.empty(argline)) { + try { + let message = window.MBCHC.add_full_stop(argline) + window.MBCHC.send_activity(`${window.CharacterNickname(window.Player)} ${message}`) + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + } + }, + }, + { Tag: "activity", + Description: "[Message]: Send a custom activity (or \"@@Message\")", + Action: (argline, cmdline, args) => { + if (!window.MBCHC.empty(argline)) { + try { + let message = window.MBCHC.add_full_stop(window.MBCHC.capitalise(argline)) + window.MBCHC.send_activity(message) + } catch (x) { window.ChatRoomSendLocal(`(Error: ${x.toString()})`) } + } + }, + }, + ], + log: function(msg) {return("MBCHC: " + msg.toString())}, + empty: function(text) { + if (!text) return(true) + if (String(text).trim().length < 1) return(true) + return(false) + }, + add_full_stop: function(text) { + if (text.endsWith('.')) return(text) + return(`${text}.`) + }, + capitalise: function(text, lower = false) { + let first = text.at(0).toLocaleUpperCase() + let rest = text.slice(1) + if (lower) rest = rest.toLocaleLowerCase() + return(first + rest) + }, + // we need this one here, this is our main loading hook + orig_AsylumGGTSSAddItems: window.AsylumGGTSSAddItems, + } // MBCHC + + // Loader + window.AsylumGGTSSAddItems = function() { + if (!window.MBCHC.LOADED) { + + // Save originals hopefully after patching + window.MBCHC.orig_ChatRoomMessageInvolvesPlayer = window.ChatRoomMessageInvolvesPlayer + window.MBCHC.orig_ChatRoomReceiveSuitcaseMoney = window.ChatRoomReceiveSuitcaseMoney + window.MBCHC.orig_ChatRoomSendChat = window.ChatRoomSendChat + + // Functions + window.MBCHC.HIDE_ALL = window.MBCHC.HIDE_SPECIAL.concat(window.MBCHC.HIDE_BODY).concat(window.MBCHC.HIDE_CLOTHES).concat(window.MBCHC.HIDE_ITEMS) + window.MBCHC.make_my_anal_hook_hide_body = function() { + let item = window.InventoryGet(window.Player, "ItemButt") + if (!item || !item.Asset || !item.Asset.Name) throw "butt seems empty" + if (item.Asset.Name !== "AnalHook") throw "butt seems occupied by something other than the anal hook" + if (!item.Property.Type || item.Property.Type !== "Hair") throw "anal hook seems not tied to hair" + item.Property = {Type: "Hair", Hide: this.HIDE_ALL} + window.CharacterRefresh(window.Player, true, true) + } + window.MBCHC.donate_data = function(id) { + if (id == window.Player.MemberNumber) throw "recipient must not be you" + const char = window.ChatRoomCharacter.find( c => c.MemberNumber == id ) + if (!char) throw "recipient not found" + if (!char.IsRestrained()) throw "recipient must be bound" + const cost = (Math.random() * 10 + 15).toFixed(0) + if (window.Player.Money < cost) throw "not enough money" + window.CharacterChangeMoney(window.Player, -cost) + window.ServerSend("ChatRoomChat", {Content: "ReceiveSuitcaseMoney", Type: "Hidden", Target: id}) + return({cost: cost, name: (char.Nickname || char.Name)}) + } + window.MBCHC.run_activity = function(char, ag, action) { + try { + char.FocusGroup = window.AssetGroupGet(char.AssetFamily, ag) + if (!char.FocusGroup) throw "invalid AssetGroup" + let activity = window.ActivityAllowedForGroup(char, char.FocusGroup.Name).find( a => a.Name === action) + if (!activity) throw "invalid activity" + window.ActivityRun(char, activity) + } finally { + char.FocusGroup = null + } + } + window.MBCHC.send_activity = function(msg) { + window.ServerSend("ChatRoomChat", {Type: "Action", Content: "lef_wie234jf_owhwi", Dictionary: [{Tag: 'MISSING PLAYER DIALOG: lef_wie234jf_owhwi', Text: msg}]}) + } + // Command actions + window.MBCHC.action_title = function(args) { + let title = args.shift() + if (!title || !title.length || title.length < 1) throw "empty title" + if (title.length > 16) throw "title too long" + if (!title.match(window.MBCHC.RE_TITLE)) throw "invalid title" + let first = title.at(0).toLocaleUpperCase() + let rest = title.slice(1).toLocaleLowerCase() + title = first + rest + window.TitleSet(title) + // TODO: this needs much more work. at least don't push a second title + // we need to patch the text cache + // we need to check for other players' custom titles + window.TitleList.push({Name: title, Requirement: () => {return true}}) + } + window.MBCHC.action_donate = function(args) { + let id = Number.parseInt(args.shift()) + if (isNaN(id)) throw "empty or invalid member number" + let result = window.MBCHC.donate_data(id) + window.ChatRoomSendLocal(`(You've bought data for $${result.cost} and sent it to ${result.name})`) + } + window.MBCHC.action_autohack = function() { + window.MBCHC.AUTOHACK_ENABLED = !window.MBCHC.AUTOHACK_ENABLED + window.ChatRoomSendLocal(`(Autohack is now ${window.MBCHC.AUTOHACK_ENABLED ? "enabled" : "disabled"})`) + } + + // Hooks + window.ChatRoomMessageInvolvesPlayer = function(data) { + if (!data.MBCHC_ID) { + data.MBCHC_ID = window.MBCHC.NEXT_MESSAGE + window.MBCHC.NEXT_MESSAGE += 1 + if (window.MBCHC.LOG_MESSAGES) console.debug(data) + if (("ReceiveSuitcaseMoney" === data.Content) && ("Hidden" === data.Type)) { window.MBCHC.LAST_HACKED = data.Sender } + } + return(window.MBCHC.orig_ChatRoomMessageInvolvesPlayer(data)) + } + window.ChatRoomReceiveSuitcaseMoney = function() { + let result = window.MBCHC.orig_ChatRoomReceiveSuitcaseMoney() + if (window.MBCHC.AUTOHACK_ENABLED && window.MBCHC.LAST_HACKED) { + window.CurrentCharacter = {MemberNumber: window.MBCHC.LAST_HACKED} + window.MBCHC.LAST_HACKED = null + window.ChatRoomTryToTakeSuitcase() + } + return(result) + } + window.ChatRoomSendChat = function() { + let input = window.ElementValue("InputChat") + if (!input.startsWith("@@@")) { + input = input.replace(/^@@/, "/activity ") + input = input.replace(/^@/, "/myself ") + window.ElementValue("InputChat", input) + } + return(window.MBCHC.orig_ChatRoomSendChat()) + } + + // Actions + window.CommandCombine(window.MBCHC.COMMANDS) + if (window.bcModSdk) window.bcModSdk.registerMod("MBCHC", window.MBCHC.VERSION) + window.MBCHC.LOADED = true + console.info(window.MBCHC.log("loaded version " + window.MBCHC.VERSION)) + } + return(window.MBCHC.orig_AsylumGGTSSAddItems()) + } // Loader +})()