2022-07-04 22:07:57 +00:00
// ==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
2022-07-07 23:10:18 +00:00
// @updateURL https://code.fleshless.org/mute/MBCHC/raw/branch/master/mbchc-local.user.js
// @downloadURL https://code.fleshless.org/mute/MBCHC/raw/branch/master/mbchc-local.user.js
2022-07-04 22:07:57 +00:00
// @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*
2022-11-03 18:17:20 +00:00
// @match http://localhost:*/*
// @match http://127.0.0.1:*/*
2022-07-04 22:07:57 +00:00
// @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
2022-08-11 00:56:40 +00:00
( 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 < i . length - 1 ; t ++ ) if ( e = e [ i [ t ]], ! n ( e )) throw new Error ( `ModSDK: Function ${ o } to be patched not found; ${ i . slice ( 0 , t + 1 ). join ( "." ) } is not object` ); const d = e [ i [ i . length - 1 ]]; if ( "function" != typeof d ) throw new Error ( `ModSDK: Function ${ o } to be patched not found` ); const c = function ( o ){ let e =- 1 ; for ( const n of t . encode ( o )){ let o = 255 & ( e ^ n ); for ( let e = 0 ; e < 8 ; e ++ ) o = 1 & o ?- 306674912 ^ o >>> 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 ( a < n . length ){ const e = n [ a ]; a ++ ; const t = null === ( s = ( c = w . errorReporterHooks ). hookEnter ) || void 0 === s ? void 0 : s . call ( c , o . name , e . mod ), r = e . hook ( d , i ); return null == t || t (), r }{ const n = null === ( f = ( l = w . errorReporterHooks ). hookChainExit ) || void 0 === f ? void 0 : f . call ( l , o . name , t . patchesSources ), a = r . apply ( this , e ); return null == n || n (), a }}; return i ( e )}}( r )} return r } function f (){ const o = new Set ; for ( const e of u . values ()) for ( const t of e . patching . keys ()) o . add ( t ); for ( const e of a . keys ()) o . add ( e ); for ( const e of o ) l ( e , ! 0 )} function p (){ const o = new Map ; for ( const [ e , t ] of a ) o . set ( e ,{ name : e , originalHash : t . originalHash , hookedByMods : r ( t . precomputed . hooks . map (( o => o . 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
2022-07-04 22:07:57 +00:00
( function () {
"use strict" ;
if ( ! window . AsylumGGTSSAddItems ) throw "AsylumGGTSSAddItems() not found, aborting MBCHC loading"
if ( window . MBCHC ) throw "MBCHC found, aborting loading"
window . MBCHC = {
2022-07-18 04:25:30 +00:00
VERSION : "trunk" ,
2022-10-18 11:46:16 +00:00
TARGET_VERSION : "R85" ,
2022-07-04 22:07:57 +00:00
NEXT_MESSAGE : 1 ,
LOG_MESSAGES : false ,
2022-07-07 23:34:05 +00:00
RETHROW : false ,
2022-07-04 22:07:57 +00:00
LOADED : false ,
AUTOHACK_ENABLED : false ,
LAST_HACKED : null ,
2022-07-14 04:28:37 +00:00
HISTORY_MODE : false ,
2022-07-04 22:07:57 +00:00
RE_TITLE : /^[a-zA-Z]+$/ ,
2022-08-11 00:56:40 +00:00
RE_PREF_ACTIVITY_ME : /^@/ ,
RE_PREF_ACTIVITY : /^@@/ ,
2022-07-08 19:32:31 +00:00
RE_ACT_CIDS : /^<(\d+)?:(\d+)?>/ ,
2022-07-05 17:53:00 +00:00
RE_TZ : /(?:GMT|UTC)([+-]\d\d?)/i ,
2022-07-08 19:32:31 +00:00
RE_ALL_LEFT : /^<+$/ ,
RE_ALL_RIGHT : /^>+$/ ,
2022-07-11 15:40:03 +00:00
RE_SPACES : /\s{2,}/g ,
RE_LAST_WORD : /(^|\s)([^\s]*)$/ ,
2022-07-14 04:28:37 +00:00
RE_LAST_LETTER : /[\w]$/ ,
2022-07-04 22:07:57 +00:00
RGB_MUTE : "#6c2132" ,
RGB_POLLY : "#81b1e7" ,
2022-07-05 01:04:07 +00:00
UTC_OFFSET : new Date (). getTimezoneOffset () * 60 * 1000 ,
2022-07-04 22:07:57 +00:00
HAND_PENETRATORS : [ "Flogger" , "Whip" , "TennisRacket" , "Gavel" , "SmallVibratingWand" , "LargeDildo" , "Vibrator" , "Hairbrush" , "SmallDildo" , "Baguette" , "Spatula" , "Broom" ],
HIDE_SPECIAL : [ "Activity" , "Emoticon" ],
2022-10-18 11:46:16 +00:00
HIDE_BODY : [ "Blush" , "BodyLower" , "BodyUpper" , "Eyebrows" , "Eyes" , "Eyes2" , "Face" , "Fluids" , "HairBack" , "HairFront" , "Hands" , "Head" , "LeftHand" , "Mouth" , "Nipples" , "Pussy" , "RightHand" ],
2022-07-04 22:07:57 +00:00
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" ,
2022-07-18 04:25:30 +00:00
"ItemArms" , "ItemNeckAccessories" , "ItemNeck" , "ItemNeckRestraints" , "ItemNipples" , "ItemNipplesPiercings" , "ItemBreast" , "ItemTorso" , "ItemTorso2" ,
2022-07-04 22:07:57 +00:00
"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": {
2022-07-07 23:10:18 +00:00
Mouth: {others: "GagKiss|Kiss|GaggedKiss"},
2022-07-05 01:04:07 +00:00
"Boots,Hands": {self: "PoliteKiss", others: "PoliteKiss|GaggedKiss"},
"Arms,Breast,Nipples": {self: "Kiss", others: "Kiss|GaggedKiss"},
2022-07-04 22:07:57 +00:00
"Butt,Ears,Feet,Head,Legs,Neck,Nose,Pelvis,Torso,Vulva,VulvaPiercings": {others: "Kiss|GaggedKiss"}
},
2022-07-05 01:04:07 +00:00
"smooch": {"Hands,Boots": {all: "Kiss"}},
2022-07-04 22:07:57 +00:00
"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"}},
2022-07-11 15:40:03 +00:00
"fuck": {"Mouth,Vulva,Butt": {others: "PenetrateSlow"}}, //peg?
2022-07-04 22:07:57 +00:00
"pound": {"Mouth,Vulva,Butt": {others: "PenetrateFast"}},
"tongue": {"Vulva,VulvaPiercings": {others: "MasturbateTongue"}},
"finger": {"Breast,Butt,Vulva,VulvaPiercings": {all: "MasturbateHand"}},
},
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"],
2022-07-07 23:10:18 +00:00
"ItemHead": ["head", "face", "hair", "eyes", "forehead"],
2022-07-04 22:07:57 +00:00
},
2022-09-29 18:00:14 +00:00
FBC_TESTER_PATCHES: [
2022-08-11 00:56:40 +00:00
[/^\^('s)?( )?/g, "^SourceCharacter$1\\s+"],
[/([^\\])\$/g, "$1\\.?$$"],
],
2022-07-11 02:42:17 +00:00
SUBCOMMANDS_MBCHC: {
"versions": {desc: "show the mod versions across the room", cb: mbchc => mbchc.inform(mbchc.gather_versions().map(c => ` < div >< b > $ { c . name } < /b> (${c.cid}): ${c.version}</div>`).join(""))},
"autohack" : { desc : "toggle the autohack feature" , cb : mbchc => mbchc . inform ( `Autohack is now ${ ( mbchc . AUTOHACK_ENABLED = ! mbchc . AUTOHACK_ENABLED ) ? "enabled" : "disabled" } ` )},
"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 (<b>WIP</b>)" , args : { TITLE : {}}, cb : ( mbchc , args ) => mbchc . title ( args [ 0 ])},
2022-07-11 19:20:43 +00:00
"tz" : { desc : "set target's UTC offset" , args : { OFFSET : {}, "[TARGET]" : {}}, cb : ( mbchc , args ) => mbchc . set_timezone ( args )},
2022-07-11 02:42:17 +00:00
"purge!" : { desc : "delete MBCHC online saved data" , cb : mbchc => { if ( window . Player . OnlineSettings . MBCHC ) { delete window . Player . OnlineSettings . MBCHC ; mbchc . save_settings ()}}},
},
2022-07-06 07:02:59 +00:00
ensure : function ( error , callback ) {
let result = callback . call ( this )
if ( ! result ) throw error
return ( result )
},
2022-07-05 17:53:00 +00:00
calculate_maps : function () {
this . DO_DATA = { verbs : {}, zones : {}}
for ( let [ verbs , data ] of Object . entries ( this . MAP_ACTIONS )) {
let unwound = {}
for ( let [ zones , actions ] of Object . entries ( data )) {
let all = ( actions . all ) ? actions . all . split ( "|" ) : []
let processed = { self : ( actions . self ) ? actions . self . split ( "|" ). concat ( all ) : all , others : ( actions . others ) ? actions . others . split ( "|" ). concat ( all ) : all }
for ( let zone of zones . split ( "," )) unwound [ `Item ${ zone } ` ] = processed
}
for ( let verb of verbs . split ( "|" )) this . DO_DATA . verbs [ verb ] = unwound
}
2022-07-08 19:32:31 +00:00
for ( let [ ag , zones ] of Object . entries ( this . MAP_ZONES )) for ( let zone of zones ) this . DO_DATA . zones [ zone ] = ag
2022-07-05 17:53:00 +00:00
},
2022-07-05 01:04:07 +00:00
settings : function ( setting = null ) {
let settings = window . Player . OnlineSettings . MBCHC || {}
return ( setting ? settings [ setting ] : settings )
},
save_settings : function ( cb = null ) {
2022-07-08 19:32:31 +00:00
if ( cb ) {
if ( ! window . Player . OnlineSettings . MBCHC ) window . Player . OnlineSettings . MBCHC = {}
cb . call ( this , window . Player . OnlineSettings . MBCHC )
}
2022-07-05 01:04:07 +00:00
window . ServerAccountUpdate . QueueData ({ OnlineSettings : window . Player . OnlineSettings })
},
2022-07-14 04:28:37 +00:00
log : function ( level , msg ) { console [ level ]( "MBCHC: " + String ( msg ))},
2022-07-04 22:07:57 +00:00
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
}
2022-07-14 04:28:37 +00:00
if ( options . dot && result . match ( this . RE_LAST_LETTER )) result = ` ${ result } .`
2022-07-04 22:07:57 +00:00
return ( result )
},
2022-08-11 00:56:40 +00:00
tokenise : function ( text ) { return text . replace ( this . RE_SPACES , " " ). split ( " " ) },
2022-07-14 04:28:37 +00:00
inform : function ( html , timeout = 60000 ) { window . ChatRoomSendLocal ( `<div class="mbchc"> ${ html } </div>` , timeout ) },
2022-07-07 23:34:05 +00:00
report : function ( x ) {
this . inform ( `Error: ${ x . toString () } ` )
if ( this . RETHROW ) throw x
},
2022-07-08 19:32:31 +00:00
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 )
2022-07-11 02:42:17 +00:00
return ( this . ensure ( `character ${ cid } not found in the room` , () => window . ChatRoomCharacter . find ( c => c . cid === cid )))
2022-07-08 19:32:31 +00:00
},
pos2char : function ( pos ) {
2022-07-14 04:28:37 +00:00
if ( ! this . in ( pos , 0 , window . ChatRoomCharacter . length - 1 )) throw `invalid position ${ pos } `
2022-07-08 19:32:31 +00:00
return ( window . ChatRoomCharacter [ pos ])
},
rel2char : function ( target ) {
2022-07-14 04:28:37 +00:00
let me = this . ensure ( "can't find my position" , () => window . ChatRoomCharacter . findIndex ( char => char . IsPlayer ()) + 1 ) - 1 // 0 is falsy, but is valid index
2022-07-11 02:42:17 +00:00
let pos = null
if ( target . match ( this . RE_ALL_LEFT )) pos = me - target . length
if ( target . match ( this . RE_ALL_RIGHT )) pos = me + target . length
if ( null === pos ) throw `failed to parse target " ${ target } "`
pos = pos % window . ChatRoomCharacter . length
if ( pos < 0 ) pos = pos + window . ChatRoomCharacter . length
return ( this . pos2char ( pos ))
2022-07-08 19:32:31 +00:00
},
2022-07-15 00:09:12 +00:00
target2char : function ( target ) { // target should be lowcase
2022-07-08 19:32:31 +00:00
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 ))
2022-07-14 04:28:37 +00:00
if ( this . in ( int , 11 , 15 )) return ( this . pos2char ( int - 11 ))
if ( this . in ( int , 21 , 25 )) return ( this . pos2char ( int - 16 ))
2022-07-15 00:09:12 +00:00
found . push (... window . ChatRoomCharacter . filter ( c => c . cid . toString (). indexOf ( target ) > - 1 ))
2022-07-08 19:32:31 +00:00
}
if ( target . startsWith ( "@" )) target = target . slice ( 1 )
2022-07-15 00:09:12 +00:00
found . push (... window . ChatRoomCharacter . filter ( c => c . Name . toLocaleLowerCase (). indexOf ( target ) > - 1 ))
2022-07-16 15:03:53 +00:00
found . push (... window . ChatRoomCharacter . filter ( c => c . Nickname && ( c . Nickname . toLocaleLowerCase (). indexOf ( target ) > - 1 )))
2022-07-08 19:32:31 +00:00
let map = {}
2022-07-11 02:42:17 +00:00
found . forEach ( c => { if ( ! map [ c . cid ]) map [ c . cid ] = c } )
2022-07-08 19:32:31 +00:00
found = Object . values ( map )
if ( found . length < 1 ) throw `target " ${ input } ": no match`
2022-07-16 15:03:53 +00:00
if ( found . length > 1 ) throw `target " ${ input } ": multiple matches ( ${ found . map ( c => ` ${ c . cid } | ${ c . Name } | ${ c . Nickname || c . Name } ` ). join ( "," ) } )`
2022-07-08 19:32:31 +00:00
return ( found [ 0 ])
},
2022-08-11 00:56:40 +00:00
char2targets : function ( char ) {
let [ result , cid ] = [ new Set (), char . cid . toString ()]
result . add ( cid ). add ( `= ${ cid } ` )
this . tokenise ( char . Name ). forEach ( t => { result . add ( t ); result . add ( `@ ${ t } ` )})
if ( char . Nickname ) this . tokenise ( char . Nickname ). forEach ( t => { result . add ( t ); result . add ( `@ ${ t } ` )})
return result
},
2022-07-08 19:32:31 +00:00
donate_data : function ( target ) {
let char = this . target2char ( target )
if ( char . IsPlayer ()) throw "target must not be you"
2022-07-06 07:02:59 +00:00
if ( ! char . IsRestrained ()) throw "target must be bound"
2022-07-14 04:28:37 +00:00
const cost = Math . round (( Math . random () * 10 + 15 ))
2022-07-04 22:07:57 +00:00
if ( window . Player . Money < cost ) throw "not enough money"
window . CharacterChangeMoney ( window . Player , - cost )
2022-07-08 19:32:31 +00:00
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 : "" }]})
2022-07-04 22:07:57 +00:00
},
run_activity : function ( char , ag , action ) { try {
2022-08-11 00:56:40 +00:00
if ( ! window . ActivityAllowed ()) throw "activities disabled in this room"
2022-07-15 16:28:26 +00:00
if ( ! window . ServerChatRoomGetAllowItem ( window . Player , char )) throw "no permissions"
2022-07-06 07:02:59 +00:00
char . FocusGroup = this . ensure ( "invalid AssetGroup" , () => window . AssetGroupGet ( char . AssetFamily , ag ))
2022-07-11 02:42:17 +00:00
let activity = this . ensure ( "invalid activity" , () => window . ActivityAllowedForGroup ( char , char . FocusGroup . Name , true ). find ( a => a . Name === action ))
2022-07-05 17:53:00 +00:00
if ( activity . Name . endsWith ( "Item" )) {
2022-11-03 18:17:20 +00:00
const item = this . ensure ( "no toy found" , () => window . Player . Appearance . find ( i . Asset ? . Name === "SpankingToys" && window . AssetSpankingToys . DynamicActivity ( char ) === activity . Name ))
window . DialogPublishAction ( char , item )
2022-07-08 19:32:31 +00:00
} else window . ActivityRun ( char , activity )
2022-07-04 22:07:57 +00:00
} finally {
char . FocusGroup = null
} },
2022-08-11 00:56:40 +00:00
replace_me : function ( match , offset , string ) {
let text = string . slice ( 1 )
let suffix = " "
if ( text . startsWith ( "'" ) || text . startsWith ( " " )) suffix = ""
return ` ${ window . MBCHC . PREF_ACTIVITY } < ${ window . Player . cid } :>SourceCharacter ${ suffix } `
},
2022-07-08 19:32:31 +00:00
cid2dict : function ( type , cid ) { return ({ Tag : ` ${ type } Character` , MemberNumber : cid , Text : this . cid2char ( cid ). dn }) },
2022-07-04 22:07:57 +00:00
send_activity : function ( msg ) {
let dict = [{ Tag : "MISSING PLAYER DIALOG: " , Text : "" }]
2022-07-08 19:32:31 +00:00
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 ]))
2022-07-04 22:07:57 +00:00
}
window . ServerSend ( "ChatRoomChat" , { Type : "Action" , Content : msg , Dictionary : dict })
},
receive : function ( data ) {
2022-07-08 19:32:31 +00:00
let char = this . cid2char ( data . Sender )
2022-10-22 12:07:11 +00:00
if ( char . IsPlayer ()) return true // this is our own message, sent back to us
2022-07-06 07:02:59 +00:00
let payload = this . ensure ( "Empty message" , () => data . Dictionary [ 0 ])
2022-07-04 22:07:57 +00:00
switch ( payload . type ) {
case "greetings" : case "hello" :
char . MBCHC = payload . value
2022-07-08 19:32:31 +00:00
if ( "greetings" === payload . type ) this . hello ( char )
2022-07-04 22:07:57 +00:00
break
default : // if we don't know the type it may be from a newer version
}
2022-10-22 12:07:11 +00:00
return true
2022-07-04 22:07:57 +00:00
},
2022-07-08 19:32:31 +00:00
hello : function ( char = null ) {
2022-07-04 22:07:57 +00:00
let payload = { type : "greetings" , value : window . Player . MBCHC }
2022-07-08 19:32:31 +00:00
if ( char ) payload . type = "hello"
2022-07-04 22:07:57 +00:00
let message = { Content : "MBCHC" , Type : "Hidden" , Dictionary : [ payload ]}
2022-07-08 19:32:31 +00:00
if ( char ) message . Target = char . cid
2022-07-04 22:07:57 +00:00
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 )
},
2022-07-11 02:42:17 +00:00
title : function ( title ) { // WIP
2022-07-04 22:07:57 +00:00
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 )
2022-07-11 02:42:17 +00:00
//window.TitleList.push({Name: title, Requirement: () => true}) // check for existing first
2022-07-04 22:07:57 +00:00
},
patch_handheld : function () {
let options = InventoryItemHandsSpankingToysOptions /* eslint-disable-line no-undef */ // window.InventoryItemHandsSpankingToysOptions is undefined
2022-07-08 19:32:31 +00:00
for ( let name of this . HAND_PENETRATORS ) {
let option = options . find ( o => o . Name === name )
2022-07-04 22:07:57 +00:00
if ( option && option . Property ) {
2022-09-29 18:00:14 +00:00
if ( ! option . Property . AllowActivity ) option . Property . AllowActivity = []
if ( option . Property . AllowActivity . indexOf ( "PenetrateItem" ) < 0 ) option . Property . AllowActivity . push ( "PenetrateItem" )
2022-07-04 22:07:57 +00:00
}
}
},
2022-09-29 18:00:14 +00:00
copy_fbc_trigger : function ( trigger ) {
2022-07-09 17:52:03 +00:00
let result = {
Type : "Action" ,
Event : trigger . Event ,
2022-09-29 18:00:14 +00:00
Matchers : trigger . Matchers . map ( m => ({ Tester : new RegExp ( this . FBC_TESTER_PATCHES . reduce (( ax ,[ f , r ]) => ax . replaceAll ( f , r ), m . Tester . source ), "u" )}))
2022-07-09 17:52:03 +00:00
}
return ( result )
},
2022-09-29 18:00:14 +00:00
patch_fbc : function () {
this . remove_fbc_hook ()
delete this . remove_fbc_hook
window . bce_ActivityTriggers . push (... window . bce_ActivityTriggers . filter ( t => "Emote" === t . Type ). 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
2022-07-15 00:09:12 +00:00
let cmd = window . Commands . find ( c => "anim" === c . Tag )
2022-09-29 18:00:14 +00:00
if ( cmd ) cmd . AutoComplete = this . complete_fbc_anim
2022-08-11 00:56:40 +00:00
cmd = window . Commands . find ( c => "pose" === c . Tag )
2022-09-29 18:00:14 +00:00
if ( cmd ) cmd . AutoComplete = this . complete_fbc_pose
2022-07-09 17:52:03 +00:00
},
2022-07-08 19:32:31 +00:00
gather_versions : function () { return ( window . ChatRoomCharacter . filter ( c => c . MBCHC ). map ( c => ({ name : c . dn , cid : c . cid , version : c . MBCHC . VERSION }))) },
2022-07-04 22:19:38 +00:00
need_load_hook : function ( module , screen ) {
if ( ! module || ! screen ) return ( true )
if (( "Character" === module ) && ( "Login" === screen )) return ( true )
return ( false )
},
2022-07-05 01:04:07 +00:00
find_timezone : function ( char ) {
let timezones = this . settings ( "timezones" )
2022-07-08 19:32:31 +00:00
if ( timezones && timezones [ char . cid ]) return ( timezones [ char . cid ])
2022-07-05 17:53:00 +00:00
let match = ( char . Description ) ? char . Description . match ( this . RE_TZ ) : null
2022-07-05 01:04:07 +00:00
if ( match ) return ( Number . parseInt ( match [ 1 ]))
return ( null )
},
2022-07-08 19:32:31 +00:00
player_enters_room : function () { // or if the mod is loaded while player is in the room
2022-07-05 01:04:07 +00:00
this . hello ()
},
set_timezone : function ( args ) {
2022-07-11 19:20:43 +00:00
let tz = Number . parseInt ( args [ 0 ])
if ( isNaN ( tz )) throw `invalid offset " ${ args [ 0 ] } "`
2022-07-08 19:32:31 +00:00
if ( ! this . in ( tz , - 12 , 12 )) throw "offset should be [-12,12]"
2022-07-11 19:20:43 +00:00
let char = this . target2char ( args [ 1 ])
2022-07-05 01:04:07 +00:00
char . MBCHC_LOCAL . TZ = tz
2022-07-11 02:42:17 +00:00
this . save_settings ( s => { if ( ! s . timezones ) s . timezones = {}; s . timezones [ char . cid ] = tz })
2022-07-05 01:04:07 +00:00
},
2022-07-09 01:56:25 +00:00
update_char : function ( char ) {
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 = this . find_timezone ( char )
},
2022-07-11 15:40:03 +00:00
command_mbchc : function ( argline , cmdline , args ) { const mbchc = window . MBCHC ; try { // `this` is command object
if ( args . length < 1 ) return ( mbchc . inform ( Object . entries ( mbchc . SUBCOMMANDS_MBCHC ). map (([ cmd , sub ]) => `<div>/mbchc ${ cmd } ${ sub . args ? Object . keys ( sub . args ). join ( " " ) : "" } : ${ sub . desc } </div>` ). join ( "" )))
2022-07-08 19:32:31 +00:00
let cmd = String ( args . shift ())
2022-07-11 15:40:03 +00:00
let sub = mbchc . ensure ( `unknown subcommand " ${ cmd } "` , () => mbchc . SUBCOMMANDS_MBCHC [ cmd ])
sub . cb . call ( mbchc , mbchc , args , argline , cmdline )
} catch ( x ) { mbchc . report ( x ) } },
command_activity : function ( argline , cmdline , args ) { const mbchc = window . MBCHC ; if ( ! mbchc . empty ( argline )) { try { // `this` is command object
let message = mbchc . normalise_message ( cmdline . replace ( mbchc . RE_ACTIVITY , '' ), { trim : true , dot : true , up : true })
mbchc . send_activity ( message )
} catch ( x ) { mbchc . report ( x ) } } },
command_do : function ( argline , cmdline , args ) { const mbchc = window . MBCHC ; try { // `this` is command object
if ( args . length < 1 ) return ( mbchc . inform ( "<div>Usage: /do VERB [ZONE] [TARGET]</div><div>Available verbs:</div>" + Object . keys ( mbchc . MAP_ACTIONS ). join ( ", " ) + "<div>Available zones:</div>" + Object . keys ( mbchc . DO_DATA . zones ). join ( ", " )))
2022-07-05 17:53:00 +00:00
let [ verb , zone , target ] = args
2022-07-11 15:40:03 +00:00
let zones = mbchc . ensure ( `unknown verb " ${ verb } "` , () => mbchc . DO_DATA . verbs [ verb ])
2022-07-06 07:02:59 +00:00
if ( 1 === Object . keys ( zones ). length ) {
if ( ! target ) target = zone
2022-07-11 15:40:03 +00:00
zone = mbchc . MAP_ZONES [ Object . keys ( zones )[ 0 ]][ 0 ]
2022-07-06 07:02:59 +00:00
}
2022-07-05 17:53:00 +00:00
if ( ! zone ) throw "zone missing"
2022-07-11 15:40:03 +00:00
let ag = mbchc . ensure ( `unknown zone " ${ zone } "` , () => mbchc . DO_DATA . zones [ zone ])
let types = mbchc . ensure ( `zone " ${ zone } " invalid for " ${ verb } "` , () => zones [ ag ])
2022-07-05 17:53:00 +00:00
let char = window . Player
2022-07-11 15:40:03 +00:00
if ( target && (( types . self . length < 1 ) || ( types . others . length > 0 ))) char = mbchc . target2char ( target )
2022-07-08 19:32:31 +00:00
let type = char . IsPlayer () ? "self" : "others"
2022-07-05 17:53:00 +00:00
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 )))
2022-07-11 15:40:03 +00:00
let actions = mbchc . ensure ( `zone " ${ zone } " invalid for (" ${ verb } " " ${ type } ")` , () => types [ type ])
let action = mbchc . ensure ( `invalid action ( ${ verb } ${ zone } ${ target } )` , () => actions . find ( name => available . find ( a => a . Name === name )))
mbchc . run_activity ( char , ag , action )
} catch ( x ) { mbchc . report ( x ) } },
2022-07-14 04:28:37 +00:00
bell : function () {
setTimeout (() => { document . getElementById ( "InputChat" ). style . outline = "" }, 100 )
document . getElementById ( "InputChat" ). style . outline = "solid red"
},
2022-07-12 03:53:07 +00:00
complete : function ( options , space = true ) {
2022-07-14 04:28:37 +00:00
if ( options . length < 1 ) return ( this . bell ())
2022-07-11 15:40:03 +00:00
if ( options . length > 1 ) {
2022-07-12 03:53:07 +00:00
let width = Math . max (... options . map ( o => o . length ))
let pref = null
for ( let i = width ; i > 0 ; i -= 1 ) { let test = options [ 0 ]. slice ( 0 , i ); if ( options . every ( o => o . startsWith ( test ))) { pref = test ; break } }
if ( pref ) this . complete ([ pref ], false )
this . complete_hint ( options )
2022-07-14 04:28:37 +00:00
} else window . ElementValue ( "InputChat" , document . getElementById ( "InputChat" ). value . replace ( this . RE_LAST_WORD , `$1 ${ options [ 0 ] }${ space ? " " : "" } ` ))
2022-07-12 03:53:07 +00:00
},
complete_hint : function ( options ) {
2022-07-14 04:28:37 +00:00
this . COMP_HINT . innerHTML = options . sort (). map ( s => `<div> ${ s } </div>` ). join ( "" )
this . COMP_HINT . style . display = "flex"
window . ElementSetDataAttribute ( this . COMP_HINT . id , "colortheme" , ( window . Player . ChatSettings . ColorTheme || "Light" ))
let rescroll = window . ElementIsScrolledToEnd ( "TextAreaChatLog" )
window . ChatRoomResize ( false )
if ( rescroll ) window . ElementScrollToEnd ( "TextAreaChatLog" )
},
comp_hint_visible : function () { return ( this . COMP_HINT . parentElement && "flex" === this . COMP_HINT . style . display )},
complete_hint_hide : function ( options ) { if ( ! this . comp_hint_visible ()) return ; this . COMP_HINT . style . display = "none" ; window . ChatRoomResize ( false )},
2022-07-15 16:28:26 +00:00
complete_target : function ( token , me2 = true , check_perms = false ) {
2022-08-11 00:56:40 +00:00
let [ locase , found ] = [ token . toLocaleLowerCase (), new Set ()]
2022-07-14 04:28:37 +00:00
for ( let c of window . ChatRoomCharacter ) {
2022-08-11 00:56:40 +00:00
if (( c . IsPlayer () && ! me2 ) || ( check_perms && ! window . ServerChatRoomGetAllowItem ( window . Player , c ))) continue
this . char2targets ( c ). forEach ( s => { if ( s . toLocaleLowerCase (). startsWith ( locase )) found . add ( s )})
2022-07-14 04:28:37 +00:00
}
2022-08-11 00:56:40 +00:00
this . complete ( Array . from ( found ))
2022-07-11 19:20:43 +00:00
},
complete_common : function () {
let input = document . getElementById ( "InputChat" ). value
2022-08-11 00:56:40 +00:00
return ([ this , input , this . tokenise ( input )])
2022-07-11 19:20:43 +00:00
},
2022-07-11 15:40:03 +00:00
complete_mbchc : function ( args , locase , cmdline ) { const [ mbchc , input , tokens ] = window . MBCHC . complete_common (); // `this` is command object
if ( tokens . length < 1 ) return
2022-07-11 19:20:43 +00:00
if ( tokens . length < 2 ) return ( mbchc . complete ([ ` ${ mbchc . CommandsKey }${ this . Tag } ` ]))
2022-08-11 00:56:40 +00:00
let subname = tokens [ 1 ]. toLocaleLowerCase ()
if ( tokens . length < 3 ) return ( mbchc . complete ( Object . keys ( mbchc . SUBCOMMANDS_MBCHC ). filter ( c => c . startsWith ( subname )))) // complete subcommand name
let sub = mbchc . SUBCOMMANDS_MBCHC [ subname ]
2022-07-11 19:20:43 +00:00
if ( sub && sub . args ) {
let argname = Object . keys ( sub . args )[ tokens . length - 3 ]
if ( "TARGET" === argname ) return ( mbchc . complete_target ( tokens [ tokens . length - 1 ], false ))
if ( "[TARGET]" === argname ) return ( mbchc . complete_target ( tokens [ tokens . length - 1 ]), true )
}
},
complete_do_target : function ( actions , token ) {
if ( ! actions ) return
2022-07-15 16:28:26 +00:00
let me2 = ( actions . self . length > 0 )
if ( me2 && actions . others . length < 1 ) return ( this . complete ([ window . Player . cid . toString ()])) // target is always the player
this . complete_target ( token , me2 , true )
2022-07-11 15:40:03 +00:00
},
complete_do : function ( args , locase , cmdline ) { const [ mbchc , input , tokens ] = window . MBCHC . complete_common (); // `this` is command object
if ( tokens . length < 1 ) return
2022-07-11 19:20:43 +00:00
if ( tokens . length < 2 ) return ( mbchc . complete ([ ` ${ mbchc . CommandsKey }${ this . Tag } ` ]))
2022-07-12 03:53:07 +00:00
// now, we *could* run a filter to exclude impossible activities, but it isn't very useful, and also seems like a lot of CPU to iterate over every action on every zone of every char in the room
2022-08-11 00:56:40 +00:00
let low = tokens [ 1 ]. toLocaleLowerCase ()
if ( tokens . length < 3 ) return ( mbchc . complete ( Object . keys ( mbchc . DO_DATA . verbs ). filter ( c => c . startsWith ( low )))) // complete verb
let ags = mbchc . DO_DATA . verbs [ low ]
if ( ! ags ) return ( mbchc . bell ())
low = tokens [ 2 ]. toLocaleLowerCase ()
2022-07-11 19:20:43 +00:00
if ( tokens . length < 4 ) { // complete zone or target
if ( Object . keys ( ags ). length < 2 ) return ( mbchc . complete_do_target ( ags [ Object . keys ( ags )[ 0 ]], tokens [ 2 ])) // zone implied, complete target
2022-08-11 00:56:40 +00:00
let zones = Object . entries ( mbchc . DO_DATA . zones ). filter (([ zone , ag ]) => zone . startsWith ( low ) && ags [ ag ]). map (([ zone , ag ]) => zone )
2022-07-11 19:20:43 +00:00
return ( mbchc . complete ( zones ))
}
if ( tokens . length < 5 ) { // complete target where it belongs
if ( Object . keys ( ags ). length < 2 ) return // zone implied, target already given
2022-08-11 00:56:40 +00:00
return ( mbchc . complete_do_target ( ags [ mbchc . DO_DATA . zones [ low ]], tokens [ 3 ]))
2022-07-11 19:20:43 +00:00
}
2022-08-11 00:56:40 +00:00
mbchc . bell ()
2022-07-11 15:40:03 +00:00
},
2022-09-29 18:00:14 +00:00
complete_fbc_anim : function ( args , locase , cmdline ) { const [ mbchc , input , tokens ] = window . MBCHC . complete_common (); // `this` is command object
2022-07-15 00:09:12 +00:00
if ( tokens . length < 1 ) return
if ( tokens . length < 2 ) return ( mbchc . complete ([ ` ${ mbchc . CommandsKey }${ this . Tag } ` ]))
2022-08-11 00:56:40 +00:00
if ( tokens . length > 2 ) return ( mbchc . bell ())
2022-07-15 00:09:12 +00:00
let anim = tokens [ 1 ]. toLocaleLowerCase ()
return ( mbchc . complete ( Object . keys ( window . bce_EventExpressions ). filter ( a => a . toLocaleLowerCase (). startsWith ( anim ))))
},
2022-09-29 18:00:14 +00:00
complete_fbc_pose : function ( args , locase , cmdline ) { const [ mbchc , input , tokens ] = window . MBCHC . complete_common (); // `this` is command object
2022-07-15 00:09:12 +00:00
if ( tokens . length < 1 ) return
if ( tokens . length < 2 ) return ( mbchc . complete ([ ` ${ mbchc . CommandsKey }${ this . Tag } ` ]))
let pose = tokens [ tokens . length - 1 ]. toLocaleLowerCase ()
return ( mbchc . complete ( window . PoseFemale3DCG . map ( p => p . Name ). filter ( p => p . toLocaleLowerCase (). startsWith ( pose ))))
},
2022-07-14 04:28:37 +00:00
history : function ( down ) {
2022-07-16 15:03:53 +00:00
let [ text , history ] = [ window . ElementValue ( "InputChat" ), window . ChatRoomLastMessage ]
if ( ! this . HISTORY_MODE ) { history . push ( text ); this . HISTORY_MODE = true }
let ids = history . map (( t , i ) => [ t , i ]). filter (([ t , i ]) => t . startsWith ( history [ history . length - 1 ])). map (([ t , i ]) => i )
if ( ! down ) ids . reverse ()
let found = ids . find ( id => ( down ) ? id > window . ChatRoomLastMessageIndex : id < window . ChatRoomLastMessageIndex )
if ( ! found ) return ( this . bell ())
window . ElementValue ( "InputChat" , history [ found ])
window . ChatRoomLastMessageIndex = found
2022-07-14 04:28:37 +00:00
},
2022-07-04 22:07:57 +00:00
loader : function () {
2022-07-07 23:10:18 +00:00
if ( this . remove_load_hook ) {
this . remove_load_hook ()
delete this . remove_load_hook
}
if ( this . LOADED ) return
2022-07-04 22:07:57 +00:00
// Calculated values
2022-07-11 15:40:03 +00:00
const COMMANDS = [
{ Tag : "mbchc" , Description : ": Utility functions (\"/mbchc\" for help)" , Action : this . command_mbchc , AutoComplete : this . complete_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 },
]
2022-07-04 22:07:57 +00:00
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 `
2022-07-14 04:28:37 +00:00
this . COMP_HINT = document . createElement ( "div" )
this . COMP_HINT . id = "mbchcCompHint"
let css = document . createElement ( "style" )
css . type = "text/css"
css . textContent = `
#TextAreaChatLog .mbchc, #TextAreaChatLog .mbchc {
background-color: ${ this . RGB_POLLY } ;
}
#TextAreaChatLog[data-colortheme="dark"] .mbchc, #TextAreaChatLog[data-colortheme="dark2"] .mbchc {
background-color: ${ this . RGB_MUTE } ;
}
# ${ this . COMP_HINT . id } {
flex-flow: column wrap;
overflow: auto;
display: none;
background-color: ${ this . RGB_POLLY } ;
color: black;
}
# ${ this . COMP_HINT . id } [data-colortheme="dark"], # ${ this . COMP_HINT . id } [data-colortheme="dark2"] {
background-color: ${ this . RGB_MUTE } ;
color: white;
}
# ${ this . COMP_HINT . id } div {
margin: 0 0.5ex;
}
`
document . head . appendChild ( css )
2022-07-04 22:07:57 +00:00
// Actions
2022-07-05 17:53:00 +00:00
this . calculate_maps ()
2022-07-04 22:07:57 +00:00
this . patch_handheld ()
window . Player . MBCHC = { VERSION : this . VERSION }
2022-07-11 15:40:03 +00:00
window . CommandCombine ( COMMANDS )
2022-07-04 22:07:57 +00:00
this . LOADED = true
2022-07-14 04:28:37 +00:00
this . log ( "info" , `loaded version ${ this . VERSION } ` )
2022-07-18 04:25:30 +00:00
if ( window . GameVersion !== this . TARGET_VERSION ) console . warn ( `Game version doesn't match the target (" ${ this . TARGET_VERSION } "), beware of incompatibilities` ) // TODO: check betas & cheat
2022-07-04 22:07:57 +00:00
}
} // MBCHC
// Hooks
window . MBCHC . sdk = window . bcModSdk . registerMod ( "MBCHC" , window . MBCHC . VERSION )
2022-07-11 15:40:03 +00:00
window . MBCHC . sdk . hookFunction ( "CharacterOnlineRefresh" , 0 , ( nextargs , next ) => {
let result = next ( nextargs )
window . MBCHC . update_char ( nextargs [ 0 ])
2022-07-07 23:10:18 +00:00
return ( result )
})
2022-07-11 15:40:03 +00:00
window . MBCHC . sdk . hookFunction ( "ChatRoomReceiveSuitcaseMoney" , 0 , ( nextargs , next ) => {
let result = next ( nextargs )
2022-07-04 22:07:57 +00:00
if ( window . MBCHC . AUTOHACK_ENABLED && window . MBCHC . LAST_HACKED ) {
2022-07-08 19:32:31 +00:00
window . CurrentCharacter = window . MBCHC . cid2char ( window . MBCHC . LAST_HACKED )
2022-07-04 22:07:57 +00:00
window . MBCHC . LAST_HACKED = null
window . ChatRoomTryToTakeSuitcase ()
}
return ( result )
})
2022-07-11 15:40:03 +00:00
window . MBCHC . sdk . hookFunction ( "ChatRoomSendChat" , 0 , ( nextargs , next ) => {
2022-07-04 22:07:57 +00:00
let input = window . ElementValue ( "InputChat" )
2022-07-14 04:28:37 +00:00
if ( ! input . startsWith ( "@@@" ) && input . startsWith ( "@" )) {
2022-07-04 22:07:57 +00:00
input = input . replace ( window . MBCHC . RE_PREF_ACTIVITY , window . MBCHC . PREF_ACTIVITY )
2022-08-11 00:56:40 +00:00
input = input . replace ( window . MBCHC . RE_PREF_ACTIVITY_ME , window . MBCHC . replace_me )
2022-07-14 04:28:37 +00:00
window . ElementValue ( "InputChat" , input )
2022-07-04 22:07:57 +00:00
}
2022-07-14 04:28:37 +00:00
let result = next ( nextargs )
let history = window . ChatRoomLastMessage
if (( history . length > 1 ) && ( history [ history . length - 1 ] === history [ history . length - 2 ])) { history . pop (); window . ChatRoomLastMessageIndex -= 1 }
return ( result )
2022-07-04 22:07:57 +00:00
})
2022-07-11 15:40:03 +00:00
window . MBCHC . sdk . hookFunction ( "ChatRoomDrawCharacterOverlay" , 0 , ( nextargs , next ) => {
let [ C , CharX , CharY , Zoom , Pos ] = nextargs
2022-07-04 22:07:57 +00:00
if (( window . ChatRoomHideIconState < 1 ) && C . MBCHC ) {
let colour = ( C . MBCHC . VERSION === window . Player . MBCHC . VERSION ) ? window . MBCHC . RGB_POLLY : window . MBCHC . RGB_MUTE
2022-07-05 01:04:07 +00:00
window . DrawRect ( CharX + 175 * Zoom , CharY , 50 * Zoom , 50 * Zoom , colour )
}
2022-07-18 04:25:30 +00:00
if (( window . ChatRoomHideIconState < 1 ) && C . MBCHC_LOCAL && C . MBCHC_LOCAL . TZ ) {
2022-10-27 17:27:42 +00:00
let hours = new Date ( window . CommonTime () + window . MBCHC . UTC_OFFSET + C . MBCHC_LOCAL . TZ * 60 * 60 * 1000 ). getHours ()
let text = ( hours < 10 ) ? "0" + hours . toString () : hours . toString ()
2022-07-05 01:04:07 +00:00
window . DrawTextFit ( text , CharX + 200 * Zoom , CharY + 25 * Zoom , 46 * Zoom , "white" , "black" )
2022-07-04 22:07:57 +00:00
}
2022-07-11 15:40:03 +00:00
return ( next ( nextargs ))
2022-07-11 02:42:17 +00:00
})
2022-07-18 04:25:30 +00:00
window . MBCHC . sdk . hookFunction ( "ElementValue" , 0 , ( nextargs , next ) => { // TODO: layer priority will be locked too if it's the same as difficulty
let [ ID , Value ] = nextargs
2022-07-11 15:40:03 +00:00
let result = next ( nextargs )
2022-07-18 04:25:30 +00:00
if (( "bce_LayerPriority" === ID ) && window . CurrentCharacter ? . FocusGroup && ( window . InventoryGet ( window . CurrentCharacter , window . CurrentCharacter . FocusGroup . Name ) ? . Difficulty . toString () === Value ) && window . InventoryLocked ( window . CurrentCharacter , window . CurrentCharacter . FocusGroup . Name , true )) window . ElementSetAttribute ( ID , "disabled" , true )
2022-07-10 07:07:09 +00:00
return ( result )
})
2022-07-14 04:28:37 +00:00
window . MBCHC . sdk . hookFunction ( "ChatRoomCreateElement" , 0 , ( nextargs , next ) => {
let result = next ( nextargs )
if ( ! window . MBCHC . COMP_HINT . parentElement ) document . body . appendChild ( window . MBCHC . COMP_HINT )
return ( result )
})
window . MBCHC . sdk . hookFunction ( "ChatRoomClearAllElements" , 0 , ( nextargs , next ) => {
window . MBCHC . complete_hint_hide ()
window . MBCHC . COMP_HINT . remove ()
return ( next ( nextargs ))
})
window . MBCHC . sdk . hookFunction ( "ChatRoomResize" , 0 , ( nextargs , next ) => {
let result = next ( nextargs )
if ( window . CharacterGetCurrent () == null && window . CurrentScreen == "ChatRoom" && document . getElementById ( "InputChat" ) && document . getElementById ( "TextAreaChatLog" ) && window . MBCHC . comp_hint_visible ()) { // upstream
let fontsize = ChatRoomFontSize /* eslint-disable-line no-undef */ // window.ChatRoomFontSize is undefined
window . ElementPositionFix ( "TextAreaChatLog" , fontsize , 1005 , 66 , 988 , 630 )
window . ElementPositionFix ( window . MBCHC . COMP_HINT . id , fontsize , 1005 , 701 , 988 , 200 )
window . MBCHC . COMP_HINT . style . display = "flex"
}
return ( result )
})
2022-07-18 04:25:30 +00:00
window . MBCHC . sdk . hookFunction ( "DocumentKeyDown" , 0 , ( nextargs , next ) => {
let [ event ] = nextargs
2022-08-11 00:56:40 +00:00
if ( "InputChat" === document . activeElement . id || "bce-message-input" === document . activeElement . id ) return ( next ( nextargs ))
2022-09-29 18:00:14 +00:00
if ( "inline" === document . getElementById ( "InputChat" ) ? . style . display && [ event . altKey , event . ctrlKey , event . metaKey ]. every ( i => ! i )) window . ElementFocus ( "InputChat" ) // alt, ctrl and meta should all be false
// TODO: this is not ideal, but it will have to do for now
2022-07-18 04:25:30 +00:00
return ( next ( nextargs ))
})
2022-07-14 04:28:37 +00:00
window . MBCHC . sdk . hookFunction ( "ChatRoomKeyDown" , 0 , ( nextargs , next ) => {
2022-07-18 04:25:30 +00:00
let [ event ] = nextargs
2022-07-14 04:28:37 +00:00
window . MBCHC . complete_hint_hide ()
2022-07-18 04:25:30 +00:00
if (( window . KeyPress == 33 ) || ( window . KeyPress == 34 )) { // better history
event . preventDefault ()
return ( window . MBCHC . history ( window . KeyPress - 33 ))
}
2022-07-14 04:28:37 +00:00
if ( window . MBCHC . HISTORY_MODE ) {
window . ChatRoomLastMessage . pop ()
window . MBCHC . HISTORY_MODE = false
}
return ( next ( nextargs ))
})
window . MBCHC . sdk . hookFunction ( "ChatRoomClick" , 0 , ( nextargs , next ) => {
window . MBCHC . complete_hint_hide ()
return ( next ( nextargs ))
})
2022-09-29 18:00:14 +00:00
window . MBCHC . remove_fbc_hook = window . MBCHC . sdk . hookFunction ( "MainRun" , 0 , ( nextargs , next ) => {
if ( window . bce_ActivityTriggers ) window . MBCHC . patch_fbc ()
2022-07-11 15:40:03 +00:00
return ( next ( nextargs ))
2022-07-09 17:52:03 +00:00
})
2022-07-04 22:07:57 +00:00
2022-10-22 12:07:11 +00:00
// Chat room handlers
window . ChatRoomRegisterMessageHandler ({ Priority : - 220 , Description : "MBCHC preprocessor" ,
Callback : ( data , sender , msg , metadata ) => {
data . MBCHC_ID = window . MBCHC . NEXT_MESSAGE
window . MBCHC . NEXT_MESSAGE += 1
if ( window . MBCHC . LOG_MESSAGES ) console . debug ({ data , sender , msg , metadata })
}
})
window . ChatRoomRegisterMessageHandler ({ Priority : - 219 , Description : "MBCHC room enter hook" ,
Callback : ( data , sender , msg , metadata ) => { if (( "Action" === data . Type ) && ( "ServerEnter" === data . Content ) && ( data . Sender === window . Player . cid )) window . MBCHC . player_enters_room () }
})
window . ChatRoomRegisterMessageHandler ({ Priority : - 219 , Description : "MBCHC specific consumer" ,
Callback : ( data , sender , msg , metadata ) => { if (( "Hidden" === data . Type ) && ( "MBCHC" === data . Content )) return window . MBCHC . receive ( data ) }
})
window . ChatRoomRegisterMessageHandler ({ Priority : - 219 , Description : "MBCHC autohack lookup" ,
Callback : ( data , sender , msg , metadata ) => { if (( "Hidden" === data . Type ) && ( "ReceiveSuitcaseMoney" === data . Content )) window . MBCHC . LAST_HACKED = data . Sender }
})
2022-07-04 22:19:38 +00:00
// MAIN SCREEN TURN ON
if ( window . MBCHC . need_load_hook ( window . CurrentModule , window . CurrentScreen )) {
2022-07-11 15:40:03 +00:00
window . MBCHC . remove_load_hook = window . MBCHC . sdk . hookFunction ( "AsylumGGTSSAddItems" , 0 , ( nextargs , next ) => { window . MBCHC . loader (); return ( next ( nextargs ))})
2022-07-04 22:19:38 +00:00
} else {
window . MBCHC . loader ()
2022-07-11 02:42:17 +00:00
if (( "Online" === window . CurrentModule ) && ( "ChatRoom" === window . CurrentScreen )) {
2022-07-09 01:56:25 +00:00
window . ChatRoomCharacter . forEach ( c => window . MBCHC . update_char ( c ))
window . MBCHC . player_enters_room ()
}
2022-07-04 22:19:38 +00:00
}
2022-07-04 22:07:57 +00:00
})()