diff --git a/mbchc-local.user.js b/mbchc-local.user.js new file mode 100644 index 0000000..754be52 --- /dev/null +++ b/mbchc-local.user.js @@ -0,0 +1,382 @@ +// ==UserScript== +// @name MBCHC-local +// @version trunk +// @description Mute's Bondage Club Hacks Collection (development version) +// @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-dev.user.js +// @downloadURL https://code.fleshless.org/mute/MBCHC/raw/branch/master/mbchc-dev.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== + +// Bondage Club Mod Development Kit (1.0.2) +// For more info see: https://github.com/Jomshir98/bondage-club-mod-sdk +/** @type {ModSDKGlobalAPI} */ // eslint-disable-next-line +var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ERROR:\n"+o);const e=new Error(o);throw console.error(e),e}const t=new TextEncoder;function n(o){return!!o&&"object"==typeof o&&!Array.isArray(o)}function r(o){const e=new Set;return o.filter((o=>!e.has(o)&&e.add(o)))}const a=new Map,i=new Set;function d(o){i.has(o)||(i.add(o),console.warn(o))}function c(o,e){if(0===e.size)return o;let t=o.toString().replaceAll("\r\n","\n");for(const[n,r]of e.entries())t.includes(n)||d(`ModSDK: Patching ${o.name}: Patch ${n} not applied`),t=t.replaceAll(n,r);return(0,eval)(`(${t})`)}function s(o){const e=[],t=new Map,n=new Set;for(const r of u.values()){const a=r.patching.get(o.name);if(a){e.push(...a.hooks);for(const[e,i]of a.patches.entries())t.has(e)&&t.get(e)!==i&&d(`ModSDK: Mod '${r.name}' is patching function ${o.name} with same pattern that is already applied by different mod, but with different pattern:\nPattern:\n${e}\nPatch1:\n${t.get(e)||""}\nPatch2:\n${i}`),t.set(e,i),n.add(r.name)}}return e.sort(((o,e)=>e.priority-o.priority)),{hooks:e,patches:t,patchesSources:n,final:c(o.original,t)}}function l(o,e=!1){let r=a.get(o);if(r)e&&(r.precomputed=s(r));else{let e=window;const i=o.split(".");for(let t=0;t>>1:o>>>1;e=e>>>8^o}return((-1^e)>>>0).toString(16).padStart(8,"0").toUpperCase()}(d.toString().replaceAll("\r\n","\n")),l={name:o,original:d,originalHash:c};r=Object.assign(Object.assign({},l),{precomputed:s(l)}),a.set(o,r),e[i[i.length-1]]=function(o){return function(...e){const t=o.precomputed,n=t.hooks,r=t.final;let a=0;const i=d=>{var c,s,l,f;if(ao.mod))),patchedByMods:Array.from(t.precomputed.patchesSources)});return o}const u=new Map;function h(o){u.get(o.name)!==o&&e(`Failed to unload mod '${o.name}': Not registered`),u.delete(o.name),o.loaded=!1}function g(o,t,r){"string"==typeof o&&o||e("Failed to register mod: Expected non-empty name string, got "+typeof o),"string"!=typeof t&&e(`Failed to register mod '${o}': Expected version string, got ${typeof t}`),r=!0===r;const a=u.get(o);a&&(a.allowReplace&&r||e(`Refusing to load mod '${o}': it is already loaded and doesn't allow being replaced.\nWas the mod loaded multiple times?`),h(a));const i=t=>{"string"==typeof t&&t||e(`Mod '${o}' failed to patch a function: Expected function name string, got ${typeof t}`);let n=c.patching.get(t);return n||(n={hooks:[],patches:new Map},c.patching.set(t,n)),n},d={unload:()=>h(c),hookFunction:(t,n,r)=>{c.loaded||e(`Mod '${c.name}' attempted to call SDK function after being unloaded`);const a=i(t);"number"!=typeof n&&e(`Mod '${o}' failed to hook function '${t}': Expected priority number, got ${typeof n}`),"function"!=typeof r&&e(`Mod '${o}' failed to hook function '${t}': Expected hook function, got ${typeof r}`);const d={mod:c.name,priority:n,hook:r};return a.hooks.push(d),f(),()=>{const o=a.hooks.indexOf(d);o>=0&&(a.hooks.splice(o,1),f())}},patchFunction:(t,r)=>{c.loaded||e(`Mod '${c.name}' attempted to call SDK function after being unloaded`);const a=i(t);n(r)||e(`Mod '${o}' failed to patch function '${t}': Expected patches object, got ${typeof r}`);for(const[n,i]of Object.entries(r))"string"==typeof i?a.patches.set(n,i):null===i?a.patches.delete(n):e(`Mod '${o}' failed to patch function '${t}': Invalid format of patch '${n}'`);f()},removePatches:o=>{c.loaded||e(`Mod '${c.name}' attempted to call SDK function after being unloaded`);i(o).patches.clear(),f()},callOriginal:(t,n,r)=>(c.loaded||e(`Mod '${c.name}' attempted to call SDK function after being unloaded`),"string"==typeof t&&t||e(`Mod '${o}' failed to call a function: Expected function name string, got ${typeof t}`),Array.isArray(n)||e(`Mod '${o}' failed to call a function: Expected args array, got ${typeof n}`),function(o,e,t=window){return l(o).original.apply(t,e)}(t,n,r)),getOriginalHash:t=>("string"==typeof t&&t||e(`Mod '${o}' failed to get hash: Expected function name string, got ${typeof t}`),l(t).originalHash)},c={name:o,version:t,allowReplace:r,api:d,loaded:!0,patching:new Map};return u.set(o,c),Object.freeze(d)}function m(){const o=[];for(const e of u.values())o.push({name:e.name,version:e.version});return o}let w;const y=void 0===window.bcModSdk?window.bcModSdk=function(){const e={version:o,apiVersion:1,registerMod:g,getModsInfo:m,getPatchingInfo:p,errorReporterHooks:Object.seal({hookEnter:null,hookChainExit:null})};return w=e,Object.freeze(e)}():(n(window.bcModSdk)||e("Failed to init Mod SDK: Name already in use"),1!==window.bcModSdk.apiVersion&&e(`Failed to init Mod SDK: Different version already loaded ('1.0.2' vs '${window.bcModSdk.version}')`),window.bcModSdk.version!==o&&alert(`Mod SDK warning: Loading different but compatible versions ('1.0.2' vs '${window.bcModSdk.version}')\nOne of mods you are using is using an old version of SDK. It will work for now but please inform author to update`),window.bcModSdk);return"undefined"!=typeof exports&&(Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=y),y}(); + +(function() { + "use strict"; + if (!window.AsylumGGTSSAddItems) throw "AsylumGGTSSAddItems() not found, aborting MBCHC loading" + if (window.MBCHC) throw "MBCHC found, aborting loading" + window.MBCHC = { + VERSION: 'trunk', + NEXT_MESSAGE: 1, + LOG_MESSAGES: false, + LOADED: false, + AUTOHACK_ENABLED: false, + LAST_HACKED: null, + RE_TITLE: /^[a-zA-Z]+$/, + RE_PREF_ACTIVITY_ME: /^@/, + RE_PREF_ACTIVITY: /^@@/, + RE_ACT_CHARS: /^<(\d+)?:(\d+)?>/, + RGB_MUTE: "#6c2132", + RGB_POLLY: "#81b1e7", + HAND_PENETRATORS: ["Flogger", "Whip", "TennisRacket", "Gavel", "SmallVibratingWand", "LargeDildo", "Vibrator", "Hairbrush", "SmallDildo", "Baguette", "Spatula", "Broom"], + 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" + ], + MAP_ACTIONS: { //ActivityFemale3DCG + // action + "nod|yes": {Head: {self: "Nod"}}, + "no": {Head: {self: "Wiggle"}}, + "moan": {Mouth: {self: "MoanGag"}}, + "mumble": {Mouth: {self: "MoanGagTalk"}}, + "whimper": {Mouth: {self: "MoanGagWhimper"}}, + "groan": {Mouth: {self: "MoanGagGroan"}}, + "scream": {Mouth: {self: "MoanGagAngry"}}, + "giggle": {Mouth: {self: "MoanGagGiggle"}}, + "struggle": {Arms: {self: "StruggleArms"}}, + "thrash": {Legs: {self: "StruggleLegs"}}, + + // action zone + "wiggle|shake": {"Arms,Breast,Boots,Butt,Ears,Feet,Hands,Nose,Pelvis,Torso": {self: "Wiggle"}}, + + // action target + "whisper": {Ears: {others: "Whisper"}}, + "choke": {Neck: {all: "Choke"}}, + "brush": {Head: {all: "TakeCare"}}, + "french": {Mouth: {others: "FrenchKiss"}}, + "sit": {Legs: {others: "Sit"}}, + "rim": {Butt: {others: "MasturbateTongue"}}, + "press": {Butt: {others: "Step"}}, + "rest": {Torso: {others: "Step"}}, + "pet": {Head: {all: "Pet"}}, + "boop": {Nose: {all: "Pet"}}, + "cuddle": {Arms: {others: "Cuddle"}}, + "nuzzle": {Nose: {others: "Cuddle"}}, + "grab": {Arms: {others: "Grope"}}, + "clean": {Mouth: {all: "Caress"}}, + "lap": {Legs: {others: "RestHead"}}, + "lean": {Breast: {others: "RestHead"}}, + "peck": {Mouth: {others: "PoliteKiss"}}, + + // action zone target + "item": { + "Breast,Butt,Feet,Legs": {all: "SpankItem|TickleItem|RubItem|RollItem|MasturbateItem|PourItem|ShockItem|Inject"}, + "Nipples,Pelvis": {all: "SpankItem|TickleItem|RubItem|RollItem|MasturbateItem|PourItem|ShockItem"}, + Arms: {all: "SpankItem|TickleItem|RubItem|RollItem|PourItem|ShockItem|Inject"}, + Boots: {all: "SpankItem|TickleItem|RubItem|RollItem|PourItem|ShockItem"}, + "Ears,Mouth": {all: "TickleItem|RubItem|RollItem"}, + "Hood,Nose": {all: "TickleItem|RubItem"}, + Neck: {all: "TickleItem|RubItem|RollItem|Inject"}, + Torso: {all: "SpankItem|TickleItem|RubItem|RollItem|PourItem|ShockItem"}, + Vulva: {all: "SpankItem|TickleItem|RubItem|MasturbateItem|ShockItem"}, + VulvaPiercings: {all: "SpankItem|TickleItem|RubItem|MasturbateItem|ShockItem|Inject"}, + }, + "kiss": { + Mouth: {others: "Kiss|GagKiss|GaggedKiss"}, + "Arms,Boots,Breast,Hands,Nipples": {self: "Kiss", others: "Kiss|GaggedKiss"}, + "Butt,Ears,Feet,Head,Legs,Neck,Nose,Pelvis,Torso,Vulva,VulvaPiercings": {others: "Kiss|GaggedKiss"} + }, + "nibble|chew": {"Arms,Hands,Boots,Mouth,Nipples": {all: "Nibble"}, "Ears,Feet,Legs,Neck,Nose,Pelvis,Torse,Vulva,VulvaPiercings": {others: "Nibble"}}, + "slap|spank": {"Head,Breast,Vulva,VulvaPiercings": {all: "Slap"}, "Arms,Boots,Butt,Feet,Hands,Legs,Pelvis,Torso": {all: "Spank"}}, + "tickle": {"Arms,Boots,Breast,Feet,Legs,Neck,Pelvis,Torso": {all: "Tickle"}}, + "massage": {"Arms,Boots,Feet,Legs,Neck,Pelvis,Torso": {all: "MassageHands"}}, + "lick": {"Arms,Boots,Breast,Hands,Mouth,Nipples": {all: "Lick"}, "Ears,Feet,Legs,Neck,Nose,Pelvis,Torso,Vulva,VulvaPiercings": {others: "Lick"}}, + "suck": {"Nipples,Hands,Boots": {all: "Suck"}}, + "bite": {"Arms,Boots,Feet,Hands,Legs,Mouth": {all: "Bite"}, "Breast,Butt,Ears,Head,Neck,Nipples,Nose,Torso": {others: "Bite"}}, + "pinch": {"Arms,Ears,Nipples,Nose,Pelvis": {all: "Pinch"}}, + "clamp": {Mouth: {all: "HandGag"}, Nose: {all: "Choke"}}, + "step": {"Breast,Neck,Pelvis": {others: "Step"}}, + "pull": {"Head,Nose,Nipples": {all: "Pull"}}, + "grope": {"Butt,Breast": {all: "Grope"}, "Feet,Legs,Pelvis": {others: "Grope"}}, + "rub": {"Head,Torso": {others: "Rub"}, Nose: {all: "Rub"}, Legs: {self: "Wiggle"}, Hands: {self: "Caress"}}, + "caress": {Hands: {others: "Caress"}, "Arms,Breast,Butt,Ears,Feet,Head,Legs,Neck,Nipples,Nose,Pelvis,Torso,Vulva,VulvaPiercings": {all: "Caress"}}, + "polish": {"Hands,Boots": {all: "TakeCare"}}, + "foot": {"Head,Nose": {others: "Step"}, "Torso,Boots": {others: "MassageFeet"}, "Vulva,VulvaPiercings": {others: "MasturbateFoot"}}, + "fist": {"Vulva,Butt": {all: "MasturbateFist"}}, + "fuck": {"Mouth,Vulva,Butt": {others: "PenetrateSlow"}}, + "pound": {"Mouth,Vulva,Butt": {others: "PenetrateFast"}}, + "tongue": {"Vulva,VulvaPiercings": {others: "MasturbateTongue"}}, + "finger": {"Breast,Butt,Vulva,VulvaPiercings": {all: "MasturbateHand"}}, + + "graze": {"Hands,Boots": {all: "PoliteKiss"}}, + }, + MAP_ZONES: { + "ItemBoots": ["foot", "feet", "boot", "boots", "shoe", "shoes", "toes", "toenails", "sole", "soles", "heel", "heels"], + "ItemFeet": ["leg", "legs", "ankle", "ankles"], + "ItemLegs": ["hips", "hip", "thighs", "thigh"], + "ItemVulva": ["vulva", "pussy"], + "ItemVulvaPiercings": ["clit", "clitoris"], + "ItemButt": ["butt", "ass"], + "ItemPelvis": ["tummy", "pelvis"], + "ItemTorso": ["body", "torso", "back", "ribs"], + "ItemBreast": ["breast", "breasts", "boob", "boobs", "booby", "boobie", "boobies", "tit", "tits", "titty", "tittie", "titties"], + "ItemNipples": ["nip", "nips", "nipple", "nipples"], + "ItemHands": ["hand", "hands", "fingers", "fingernails", "nails"], + "ItemArms": ["arm", "arms", "elbow", "elbows"], + "ItemNeck": ["neck"], + "ItemMouth": ["mouth", "lip", "lips", "teeth", "tongue", "gag", "cheek", "cheeks"], + "ItemNose": ["nose", "nostrils"], + "ItemEars": ["ear", "ears", "earlobe", "earlobes"], + "ItemHead": ["head", "face", "hair", "eyes"], + }, + USAGE_MBCHC_LINES: [ + "
Usage: /mbchc SUBCOMMAND [ARGS...]
", + "
/mbchc versions : show the mod versions across the room
", + "
/mbchc autohack : toggle the autohack feature
", + "
/mbchc disappear : become invisible (requires anal hook -> hair)
", + "
/mbchc donate MEMBERNUMBER : Buy data and send it to recipient
", + "
/mbchc title TITLE : set a custom title (WIP)
", + ], + COMMANDS: [ + { Tag: "mbchc", Description: ": Utility functions (\"/mbchc\" for help)", Action: (argline, cmdline, args) => { window.MBCHC.command_mbchc(argline, cmdline, args) } }, + { Tag: "activity", Description: "[Message]: Send a custom activity (or \"@@Message\", or \"@Message\" as yourself)", Action: (argline, cmdline, args) => { window.MBCHC.command_activity(argline, cmdline, args) } }, + { Tag: "do", Description: ": Do an activity, as if clicked on its button (\"/do\" for help)", Action: (argline, cmdline, args) => { window.MBCHC.command_do(argline, cmdline, args) } }, + ], + log: function(msg) {return("MBCHC: " + String(msg))}, + empty: function(text) { + if (!text) return(true) + if (String(text).trim().length < 1) return(true) + return(false) + }, + normalise_message: function(text, options = {}) { + let result = text + if (options.trim) result = result.trim() + if (options.low) result = result.toLocaleLowerCase() + if (options.up) { + let first = result.at(0).toLocaleUpperCase() + let rest = result.slice(1) + result = first + rest + } + if (options.dot && result.match(/[\w]$/)) result = `${result}.` + return(result) + }, + inform: function(html) { window.ChatRoomSendLocal(`
${html}
`, 60000) }, + report: function(x) { this.inform(`Error: ${x.toString()}`) }, + id2char: function(id) { return(window.ChatRoomCharacter.find( c => c.MemberNumber === Number.parseInt(id) )) }, + donate_data: function(recipient) { + let id = Number.parseInt(recipient) + if (isNaN(id)) throw "empty or invalid recipient" + if (id === window.Player.MemberNumber) throw "recipient must not be you" + const char = this.id2char(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: window.CharacterNickname(char)}) + }, + 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 + } }, + char2dict: function(type, id) { return({Tag: `${type}Character`, MemberNumber: id, Text: window.CharacterNickname(this.id2char(id))}) }, + send_activity: function(msg) { + let dict = [{Tag: "MISSING PLAYER DIALOG: ", Text: ""}] + let chars = msg.match(this.RE_ACT_CHARS) + if (chars) { + msg = msg.replace(this.RE_ACT_CHARS, "") + if (chars[1]) dict.push(this.char2dict("Source", chars[1])) + if (chars[2]) dict.push(this.char2dict("Target", chars[2]), this.char2dict("Destination", chars[2])) + } + window.ServerSend("ChatRoomChat", {Type: "Action", Content: msg, Dictionary: dict}) + }, + receive: function(data) { + if (data.Sender === window.Player.MemberNumber) return // this is our own message, sent back to us + let char = this.id2char(data.Sender) + if (!char) throw `Invalid message sender: ${data.Sender}` + let payload = data.Dictionary[0] + if (!payload) throw "Empty message" + switch (payload.type) { + case "greetings": case "hello": + char.MBCHC = payload.value + if ("greetings" === payload.type) this.hello(char.MemberNumber) + break + default: // if we don't know the type it may be from a newer version + } + }, + hello: function(target = null) { + let payload = {type: "greetings", value: window.Player.MBCHC} + if (target) payload.type = "hello" + let message = {Content: "MBCHC", Type: "Hidden", Dictionary: [payload]} + if (target) message.Target = target + window.ServerSend("ChatRoomChat", message) + }, + disappear: 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) + }, + title: function(args) { + let title = args.shift() + if (this.empty(title)) throw "empty title" + title = this.normalise_message(title, {trim: true, up: true, low: true}) + if (title.length > 16) throw "title too long" + if (!title.match(this.RE_TITLE)) throw "invalid title" + 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}}) + }, + patch_handheld: function() { + let options = InventoryItemHandsSpankingToysOptions /* eslint-disable-line no-undef */ // window.InventoryItemHandsSpankingToysOptions is undefined + for (let i in this.HAND_PENETRATORS) { + let option = options.find(o => o.Name === this.HAND_PENETRATORS[i]) + if (option && option.Property) { + if (!option.Property.Attribute) option.Property.Attribute = [] + if (option.Property.Attribute.indexOf("PenetrateItem") < 0) option.Property.Attribute.push("PenetrateItem") + } + } + }, + gather_versions: function() { + let result = [] + for (let i in window.ChatRoomCharacter) { + let c = window.ChatRoomCharacter[i] + if (c.MBCHC) result.push({name: window.CharacterNickname(c), id: c.MemberNumber, version: c.MBCHC.VERSION}) + } + return(result) + }, + + // Command actions + command_mbchc: function(argline, cmdline, args) { try { + if (args.length < 1) return(this.inform(this.USAGE_MBCHC_LINES.join(""))) + let cmd = String(args.shift()).toLocaleLowerCase() + switch (cmd) { + case "versions": this.inform(this.gather_versions().map(c => { return(`
${c.name} (${c.id}): ${c.version}
`) }).join("")); break + case "disappear": this.disappear(); break + case "title": this.title(args); break + case "autohack": + this.AUTOHACK_ENABLED = !this.AUTOHACK_ENABLED + this.inform(`Autohack is now ${this.AUTOHACK_ENABLED ? "enabled" : "disabled"}`) + break + case "donate": + var result = this.donate_data(args[0]) + window.ChatRoomMessage({Sender: window.Player.MemberNumber, Type: "Action", Content: `You've bought data for $${result.cost} and sent it to ${result.name}.`, Dictionary: [{Tag: "MISSING PLAYER DIALOG: ", Text: ""}]}) + break + default: throw `unknown subcommand "${cmd}"` + } + } catch (x) { this.report(x) } }, + command_activity: function(argline, cmdline, args) { if (!this.empty(argline)) { try { + let message = this.normalise_message(cmdline.replace(this.RE_ACTIVITY, ''), {trim: true, dot: true, up: true}) + this.send_activity(message) + } catch (x) { this.report(x) } } }, + command_do: function(argline, cmdline, args) { try { + if (args.length < 1) { + } + let char = window.Player + if ("-" !== args[0]) { + let id = Number.parseInt(args[0]) + let char = this.id2char(id) + } + if (!char) throw "invalid member number" + if (args.length < 2) { + } + if (args.length < 3) { + } + let ag = args[1] + let action = args[2] + this.run_activity(char, ag, action) + } catch (x) { this.report(x) } }, + loader: function() { + if (this.LOADED) return //FIXME: unhook if hooked, no need to call this every time + // Calculated values + 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 = RegExp(`^${this.CommandsKey}activity `) + this.PREF_ACTIVITY = `${this.CommandsKey}activity ` + // Actions + this.patch_handheld() + window.Player.MBCHC = {VERSION: this.VERSION} + window.CommandCombine(this.COMMANDS) + this.LOADED = true + console.info(this.log(`loaded version ${this.VERSION}`)) + } + } // MBCHC + + // Hooks + window.MBCHC.sdk = window.bcModSdk.registerMod("MBCHC", window.MBCHC.VERSION) + window.MBCHC.sdk.hookFunction("ChatRoomMessageInvolvesPlayer", 0, (args, next) => { + let data = args[0] + 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 } + if (("ServerEnter" === data.Content) && ("Action" === data.Type) && (data.Sender === window.Player.MemberNumber)) { window.MBCHC.hello() } + if (("MBCHC" === data.Content) && ("Hidden" === data.Type)) { window.MBCHC.receive(data) } + } + return(next(args)) + }) + window.MBCHC.sdk.hookFunction("ChatRoomReceiveSuitcaseMoney", 0, (args, next) => { + let result = next(args) + 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.MBCHC.sdk.hookFunction("ChatRoomSendChat", 0, (args, next) => { + let input = window.ElementValue("InputChat") + if (!input.startsWith("@@@")) { + input = input.replace(window.MBCHC.RE_PREF_ACTIVITY, window.MBCHC.PREF_ACTIVITY) + input = input.replace(window.MBCHC.RE_PREF_ACTIVITY_ME, `${window.MBCHC.PREF_ACTIVITY} <${window.Player.MemberNumber}:>SourceCharacter `) + } + window.ElementValue("InputChat", input) + return(next(args)) + }) + window.MBCHC.sdk.hookFunction("ChatRoomDrawCharacterOverlay", 0, (args, next) => { + let [C, CharX, CharY, Zoom, Pos] = args + if ((window.ChatRoomHideIconState < 1) && C.MBCHC) { + let colour = (C.MBCHC.VERSION === window.Player.MBCHC.VERSION) ? window.MBCHC.RGB_POLLY : window.MBCHC.RGB_MUTE + window.DrawRect(CharX + 225 * Zoom, CharY, 50 * Zoom, 50 * Zoom, colour) + } + return(next(args)) + }) + /* Things to keep in mind: + 1. mod may be loaded early or late + 2. if we're loaded past login screen, no need to hook anything, load directly + 3. if we're loaded into a chatroom we need to send greetings (should probably be in the loader function) + */ +// if (!window.CurrentModule && !window.CurrentScreen) { + window.MBCHC.sdk.hookFunction("AsylumGGTSSAddItems", 0, (args, next) => { window.MBCHC.loader(); return(next(args)) }) + +console.debug({module: window.CurrentModule, screen: window.CurrentScreen}) + +})()