diff --git a/mbchc-local.user.js b/mbchc-local.user.js index 8551559..c2e0d67 100644 --- a/mbchc-local.user.js +++ b/mbchc-local.user.js @@ -34,8 +34,10 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER RE_TITLE: /^[a-zA-Z]+$/, RE_PREF_ACTIVITY_ME: /^@/, RE_PREF_ACTIVITY: /^@@/, - RE_ACT_CHARS: /^<(\d+)?:(\d+)?>/, + RE_ACT_CIDS: /^<(\d+)?:(\d+)?>/, RE_TZ: /(?:GMT|UTC)([+-]\d\d?)/i, + RE_ALL_LEFT: /^<+$/, + RE_ALL_RIGHT: /^>+$/, RGB_MUTE: "#6c2132", RGB_POLLY: "#81b1e7", UTC_OFFSET: new Date().getTimezoneOffset() * 60 * 1000, @@ -180,15 +182,17 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER } for (let verb of verbs.split("|")) this.DO_DATA.verbs[verb] = unwound } - for (let [ag, zones] of Object.entries(this.MAP_ZONES)) for (let i in zones) this.DO_DATA.zones[zones[i]] = ag + for (let [ag, zones] of Object.entries(this.MAP_ZONES)) for (let zone of zones) this.DO_DATA.zones[zone] = ag }, settings: function(setting = null) { let settings = window.Player.OnlineSettings.MBCHC || {} return(setting ? settings[setting] : settings) }, save_settings: function(cb = null) { - if (!window.Player.OnlineSettings.MBCHC) window.Player.OnlineSettings.MBCHC = {} - if (cb) cb.call(this, window.Player.OnlineSettings.MBCHC) + if (cb) { + if (!window.Player.OnlineSettings.MBCHC) window.Player.OnlineSettings.MBCHC = {} + cb.call(this, window.Player.OnlineSettings.MBCHC) + } window.ServerAccountUpdate.QueueData({OnlineSettings: window.Player.OnlineSettings}) }, log: function(msg) {return("MBCHC: " + String(msg))}, @@ -215,18 +219,59 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER this.inform(`Error: ${x.toString()}`) if (this.RETHROW) throw x }, - 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 target" - if (id === window.Player.MemberNumber) throw "target must not be you" - const char = this.ensure("target not found", () => this.id2char(id)) + in: function(x, floor, ceiling) { return((x >= floor) && (x <= ceiling)) }, + cid2char: function(cid) { + cid = Number.parseInt(cid) + if (cid === window.Player.cid) return(window.Player) + return(this.ensure(`character ${cid} not found in the room`, () => window.ChatRoomCharacter.find( c => c.cid === cid ))) + }, + pos2char: function(pos) { + if (pos >= window.ChatRoomCharacter.length) throw `invalid position ${pos}` + return(window.ChatRoomCharacter[pos]) + }, + rel2char: function(target) { + let me = this.ensure("can't find my position", () => window.ChatRoomCharacter.findIndex(char => char.IsPlayer())) + let index = null + if (target.match(this.RE_ALL_LEFT)) index = me - target.length + if (target.match(this.RE_ALL_RIGHT)) index = me + target.length + if (null === index) throw `failed to parse target "${target}"` + index = index % window.ChatRoomCharacter.length + if (index < 0) index = window.ChatRoomCharacter.length + index + return(window.ChatRoomCharacter[index]) + }, + target2char: function(target) { + let input = target + if (this.empty(target)) return(window.Player) + let int = Number.parseInt(target) + target = String(target) + let found = [] + if (target.startsWith("=")) return(this.cid2char(target.slice(1))) + if (target.startsWith("<") || target.startsWith(">")) return(this.rel2char(target)) + if (!isNaN(int) && int.toString() === target) { // we got a number + if (this.in(int, 0, 9)) return(this.pos2char(int)) + if (this.in(int, 11, 15)) return(this.pos2char((int % 10) - 1)) + if (this.in(int, 21, 25)) return(this.pos2char((5 + int % 20) - 1)) + found = found.concat(window.ChatRoomCharacter.filter(c => c.cid.toString().indexOf(target) > -1)) + } + if (target.startsWith("@")) target = target.slice(1) + found = found.concat(window.ChatRoomCharacter.filter(c => c.Name.toLocaleLowerCase().indexOf(target) > -1)) + found = found.concat(window.ChatRoomCharacter.filter(c => c.Nickname.toLocaleLowerCase().indexOf(target) > -1)) + let map = {} + found.forEach(c => { if (!map[c.cid]) map[c.cid] = c } ) + found = Object.values(map) + if (found.length < 1) throw `target "${input}": no match` + if (found.length > 1) throw `target "${input}": multiple matches (${found.map(c => `${c.cid}|${c.Name}|${c.Nickname}`).join(",")})` + return(found[0]) + }, + donate_data: function(target) { + let char = this.target2char(target) + if (char.IsPlayer()) throw "target must not be you" if (!char.IsRestrained()) throw "target 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)}) + 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: ""}]}) }, run_activity: function(char, ag, action) { try { char.FocusGroup = this.ensure("invalid AssetGroup", () => window.AssetGroupGet(char.AssetFamily, ag)) @@ -234,40 +279,38 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER if (activity.Name.endsWith("Item")) { let item = this.ensure("no toy found", () => window.Player.Inventory.find(i => (i.Asset != null) && (i.Asset.Group.Name == char.FocusGroup.Name) && i.Asset.DynamicAllowInventoryAdd(char))) window.DialogPublishAction(char, window.DialogInventoryCreateItem(char, item, false)) - } else { - window.ActivityRun(char, activity) - } + } else window.ActivityRun(char, activity) } finally { char.FocusGroup = null } }, - char2dict: function(type, id) { return({Tag: `${type}Character`, MemberNumber: id, Text: window.CharacterNickname(this.id2char(id))}) }, + cid2dict: function(type, cid) { return({Tag: `${type}Character`, MemberNumber: cid, Text: this.cid2char(cid).dn}) }, 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])) + let cids = msg.match(this.RE_ACT_CIDS) + if (cids) { + msg = msg.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: 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.ensure(`Invalid message sender: ${data.Sender}`, () => this.id2char(data.Sender)) + let char = this.cid2char(data.Sender) + if (char.IsPlayer()) return // this is our own message, sent back to us let payload = this.ensure("Empty message", () => data.Dictionary[0]) switch (payload.type) { case "greetings": case "hello": char.MBCHC = payload.value - if ("greetings" === payload.type) this.hello(char.MemberNumber) + if ("greetings" === payload.type) this.hello(char) break default: // if we don't know the type it may be from a newer version } }, - hello: function(target = null) { + hello: function(char = null) { let payload = {type: "greetings", value: window.Player.MBCHC} - if (target) payload.type = "hello" + if (char) payload.type = "hello" let message = {Content: "MBCHC", Type: "Hidden", Dictionary: [payload]} - if (target) message.Target = target + if (char) message.Target = char.cid window.ServerSend("ChatRoomChat", message) }, disappear: function() { @@ -289,22 +332,15 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER }, 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]) + for (let name of this.HAND_PENETRATORS) { + let option = options.find(o => o.Name === name) 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) - }, + gather_versions: function() { return(window.ChatRoomCharacter.filter(c => c.MBCHC).map(c => ({name: c.dn, cid: c.cid, version: c.MBCHC.VERSION}))) }, need_load_hook: function(module, screen) { if (!module || !screen) return(true) if (("Character" === module) && ("Login" === screen)) return(true) @@ -312,39 +348,33 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER }, find_timezone: function(char) { let timezones = this.settings("timezones") - if (timezones && timezones[char.MemberNumber]) return(timezones[char.MemberNumber]) + if (timezones && timezones[char.cid]) return(timezones[char.cid]) let match = (char.Description) ? char.Description.match(this.RE_TZ) : null if (match) return(Number.parseInt(match[1])) return(null) }, - player_enters_room: function() { + player_enters_room: function() { // or if the mod is loaded while player is in the room this.hello() }, set_timezone: function(args) { - let char = this.ensure("invalid target", () => this.id2char(args[0])) + let char = this.cid2char(args[0]) let tz = Number.parseInt(args[1]) if (isNaN(tz)) throw "invalid offset" - if ((tz < -12) || (tz > 12)) throw "offset should be [-12,12]" + if (!this.in(tz, -12, 12)) throw "offset should be [-12,12]" char.MBCHC_LOCAL.TZ = tz - this.save_settings((s) => { if (!s.timezones) s.timezones = {}; s.timezones[char.MemberNumber] = tz }) + this.save_settings((s) => { if (!s.timezones) s.timezones = {}; s.timezones[char.cid] = tz }) }, // 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() + let cmd = String(args.shift()) switch (cmd) { - case "versions": this.inform(this.gather_versions().map(c => { return(`
${c.name} (${c.id}): ${c.version}
`) }).join("")); break + case "versions": this.inform(this.gather_versions().map(c => { return(`
${c.name} (${c.cid}): ${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 + case "autohack": this.inform(`Autohack is now ${(this.AUTOHACK_ENABLED = !this.AUTOHACK_ENABLED) ? "enabled" : "disabled"}`); break + case "donate": this.donate_data(args[0]); break case "tz": this.set_timezone(args); break case "purge!": if (window.Player.OnlineSettings.MBCHC) { @@ -372,8 +402,8 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER let ag = this.ensure(`unknown zone "${zone}"`, () => this.DO_DATA.zones[zone]) let types = this.ensure(`zone "${zone}" invalid for "${verb}"`, () => zones[ag]) let char = window.Player - if (target && ((types.self.length < 1) || (types.others.length > 0))) char = this.ensure("invalid target", () => this.id2char(target)) - let type = (char.MemberNumber === window.Player.MemberNumber) ? "self" : "others" + if (target && ((types.self.length < 1) || (types.others.length > 0))) char = this.target2char(target) + let type = char.IsPlayer() ? "self" : "others" let available = window.ActivityAllowedForGroup(char, ag) let toy = window.InventoryGet(window.Player, "ItemHands") if (toy && toy.Asset.Name === "SpankingToys") available.push(window.AssetAllActivities(char.AssetFamily).find(a => a.Name === window.InventorySpankingToysGetActivity(window.Player))) @@ -407,6 +437,8 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER window.MBCHC.sdk.hookFunction("CharacterOnlineRefresh", 0, (args, next) => { let result = next(args) let char = args[0] + char.cid = char.MemberNumber // Club ID (shorter) + char.dn = window.CharacterNickname(char) // DisplayName (shortcut) if (!char.MBCHC_LOCAL) char.MBCHC_LOCAL = {} if (!char.MBCHC_LOCAL.TZ) char.MBCHC_LOCAL.TZ = window.MBCHC.find_timezone(char) return(result) @@ -418,7 +450,7 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER 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.player_enters_room() } + if (("ServerEnter" === data.Content) && ("Action" === data.Type) && (data.Sender === window.Player.cid)) { window.MBCHC.player_enters_room() } if (("MBCHC" === data.Content) && ("Hidden" === data.Type)) { window.MBCHC.receive(data) } } return(next(args)) @@ -426,7 +458,7 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER 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.CurrentCharacter = window.MBCHC.cid2char(window.MBCHC.LAST_HACKED) window.MBCHC.LAST_HACKED = null window.ChatRoomTryToTakeSuitcase() } @@ -436,7 +468,7 @@ var bcModSdk=function(){"use strict";const o="1.0.2";function e(o){alert("Mod ER 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 `) + input = input.replace(window.MBCHC.RE_PREF_ACTIVITY_ME, `${window.MBCHC.PREF_ACTIVITY} <${window.Player.cid}:>SourceCharacter `) } window.ElementValue("InputChat", input) return(next(args))