2023-12-17 03:52:43 +00:00
export { }
2023-12-19 01:15:39 +00:00
// Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsy value
const MISSING _PLAYER _DIALOG = { Tag : 'MISSING PLAYER DIALOG: ' , Text : '\u200C' }
2023-12-17 03:52:43 +00:00
if ( window . MBCHC ) throw new Error ( 'MBCHC found, aborting loading' )
window . MBCHC = {
VERSION : 'dev.9' ,
NEXT _MESSAGE : 1 ,
LOG _MESSAGES : false ,
RETHROW : false ,
LOADED : false ,
AUTOHACK _ENABLED : false ,
LAST _HACKED : null ,
HISTORY _MODE : false ,
2023-12-19 01:15:39 +00:00
// RE_TITLE: /^[a-zA-Z]+$/,
2023-12-17 03:52:43 +00:00
RE _PREF _ACTIVITY _ME : /^@/ ,
RE _PREF _ACTIVITY : /^@@/ ,
RE _ACT _CIDS : /^<(\d+)?:(\d+)?>/ ,
RE _TZ : /(?:gmt|utc)\s*([+-])\s*(\d\d?)/i ,
RE _ALL _LEFT : /^<+$/ ,
RE _ALL _RIGHT : /^>+$/ ,
RE _SPACES : /\s{2,}/g ,
RE _LAST _WORD : /(^|\s)(\S*)$/ ,
RE _LAST _LETTER : /\w$/ ,
RGB _MUTE : '#6c2132' ,
RGB _POLLY : '#81b1e7' ,
UTC _OFFSET : new Date ( ) . getTimezoneOffset ( ) * 60 * 1000 ,
2023-12-19 01:15:39 +00:00
// HIDE_SPECIAL: ['Activity', 'Emoticon'],
// HIDE_BODY: ['Blush', 'BodyLower', 'BodyUpper', 'Eyebrows', 'Eyes', 'Eyes2', 'Face', 'Fluids', 'HairBack', 'HairFront', 'Hands', 'Head', 'LeftHand', 'Mouth', 'Nipples', 'Pussy', 'RightHand'],
// HIDE_CLOTHES: [
// 'Cloth',
// 'ClothAccessory',
// 'Necklace',
// 'Suit',
// 'ClothLower',
// 'SuitLower',
// 'Bra',
// 'Corset',
// 'Panties',
// 'Socks',
// 'RightAnklet',
// 'LeftAnklet',
// 'Garters',
// 'Shoes',
// 'Hat',
// 'HairAccessory3',
// 'HairAccessory1',
// 'HairAccessory2',
// 'Gloves',
// 'Bracelet',
// 'Glasses',
// 'Mask',
// 'TailStraps',
// 'Wings',
// ],
// HIDE_ITEMS: [
// 'ItemMisc',
// 'ItemEars',
// 'ItemHead',
// 'ItemNose',
// 'ItemHood',
// 'ItemAddon',
// 'ItemMouth',
// 'ItemMouth2',
// 'ItemMouth3',
// 'ItemArms',
// 'ItemNeckAccessories',
// 'ItemNeck',
// 'ItemNeckRestraints',
// 'ItemNipples',
// 'ItemNipplesPiercings',
// 'ItemBreast',
// 'ItemTorso',
// 'ItemTorso2',
// 'ItemHands',
// 'ItemPelvis',
// 'ItemVulva',
// 'ItemVulvaPiercings',
// 'ItemDevices',
// 'ItemLegs',
// 'ItemFeet',
// 'ItemBoots',
// ],
2023-12-17 03:52:43 +00:00
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 : 'GagKiss|Kiss|GaggedKiss' } ,
'Boots,Hands' : { self : 'PoliteKiss' , others : 'PoliteKiss|GaggedKiss' } ,
'Arms,Breast,Nipples' : { self : 'Kiss' , others : 'Kiss|GaggedKiss' } ,
'Butt,Ears,Feet,Head,Legs,Neck,Nose,Pelvis,Torso,Vulva,VulvaPiercings' : { others : 'Kiss|GaggedKiss' } ,
} ,
smooch : { 'Hands,Boots' : { all : 'Kiss' } } ,
'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' } } , // Peg?
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' ] ,
ItemHead : [ 'head' , 'face' , 'hair' , 'eyes' , 'forehead' ] ,
} ,
FBC _TESTER _PATCHES : [
[ /^\^('s)?( )?/g , '^SourceCharacter$1\\s+' ] ,
[ /([^\\])\$/g , '$1\\.?$$' ] ,
] ,
SUBCOMMANDS _MBCHC : {
2023-12-19 01:15:39 +00:00
// 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(''))},
2023-12-17 03:52:43 +00:00
autohack : { desc : 'toggle the autohack feature' , cb : mbchc => mbchc . inform ( ` Autohack is now ${ ( mbchc . AUTOHACK _ENABLED = ! mbchc . AUTOHACK _ENABLED ) ? 'enabled' : 'disabled' } ` ) } , // eslint-disable-line no-cond-assign
2023-12-19 01:15:39 +00:00
// disappear: {desc: 'become invisible (requires anal hook -> hair)', cb: mbchc => mbchc.disappear()},
2023-12-17 03:52:43 +00:00
donate : { desc : 'Buy data and send it to recipient' , args : { TARGET : { } } , cb : ( mbchc , args ) => mbchc . donate _data ( args [ 0 ] ) } ,
2023-12-19 01:15:39 +00:00
// title: {desc: 'set a custom title (<b>WIP</b>)', args: {TITLE: {}}, cb: (mbchc, args) => mbchc.title(args[0])},
2023-12-17 03:52:43 +00:00
tz : { desc : 'set target\'s UTC offset' , args : { OFFSET : { } , '[TARGET]' : { } } , cb : ( mbchc , args ) => mbchc . set _timezone ( args ) } ,
'purge!' : { desc : 'delete MBCHC online saved data' , cb ( mbchc ) {
if ( window . Player . OnlineSettings . MBCHC ) {
delete window . Player . OnlineSettings . MBCHC
mbchc . save _settings ( )
}
} } ,
} ,
ensure ( error , callback ) {
const result = callback . call ( this )
if ( ! result ) throw error
return ( result )
} ,
calculate _maps ( ) {
this . DO _DATA = { verbs : { } , zones : { } }
for ( const [ verbs , data ] of Object . entries ( this . MAP _ACTIONS ) ) {
const unwound = { }
for ( const [ zones , actions ] of Object . entries ( data ) ) {
const all = ( actions . all ) ? actions . all . split ( '|' ) : [ ]
const processed = { self : ( actions . self ) ? actions . self . split ( '|' ) . concat ( all ) : all , others : ( actions . others ) ? actions . others . split ( '|' ) . concat ( all ) : all }
for ( const zone of zones . split ( ',' ) ) unwound [ ` Item ${ zone } ` ] = processed
}
for ( const verb of verbs . split ( '|' ) ) this . DO _DATA . verbs [ verb ] = unwound
}
for ( const [ ag , zones ] of Object . entries ( this . MAP _ZONES ) ) for ( const zone of zones ) this . DO _DATA . zones [ zone ] = ag
} ,
settings ( setting = null ) {
const settings = window . Player . OnlineSettings . MBCHC || { }
return ( setting ? settings [ setting ] : settings )
} ,
save _settings ( cb = null ) {
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 ( level , message ) {
console [ level ] ( 'MBCHC: ' + String ( message ) )
} ,
empty ( text ) {
if ( ! text ) return ( true )
if ( String ( text ) . trim ( ) . length === 0 ) return ( true )
return ( false )
} ,
normalise _message ( text , options = { } ) {
let result = text
if ( options . trim ) result = result . trim ( )
if ( options . low ) result = result . toLocaleLowerCase ( )
if ( options . up ) {
const first = result . at ( 0 ) . toLocaleUpperCase ( )
const rest = result . slice ( 1 )
result = first + rest
}
if ( options . dot && this . RE _LAST _LETTER . test ( result ) ) result = ` ${ result } . `
return ( result )
} ,
tokenise ( text ) {
return text . replace ( this . RE _SPACES , ' ' ) . split ( ' ' )
} ,
inform ( html , timeout = 60_000 ) {
window . ChatRoomSendLocal ( ` <div class="mbchc"> ${ html } </div> ` , timeout )
} ,
report ( x ) {
this . inform ( ` Error: ${ x . toString ( ) } ` )
if ( this . RETHROW ) throw x
} ,
in ( x , floor , ceiling ) {
return ( ( x >= floor ) && ( x <= ceiling ) )
} ,
cid2char ( cid ) {
cid = Number . parseInt ( cid , 10 )
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 ( pos ) {
if ( ! this . in ( pos , 0 , window . ChatRoomCharacter . length - 1 ) ) throw new Error ( ` invalid position ${ pos } ` )
return ( window . ChatRoomCharacter [ pos ] )
} ,
rel2char ( target ) {
const me = this . ensure ( 'can\'t find my position' , ( ) => window . ChatRoomCharacter . findIndex ( char => char . IsPlayer ( ) ) + 1 ) - 1 // 0 is falsy, but is valid index
let pos = null
if ( this . RE _ALL _LEFT . test ( target ) ) pos = me - target . length
if ( this . RE _ALL _RIGHT . test ( target ) ) pos = me + target . length
if ( pos === null ) throw new Error ( ` failed to parse target " ${ target } " ` )
pos %= window . ChatRoomCharacter . length
if ( pos < 0 ) pos += window . ChatRoomCharacter . length
return ( this . pos2char ( pos ) )
} ,
target2char ( target ) { // Target should be lowcase
const input = target
if ( this . empty ( target ) ) return ( window . Player )
const int = Number . parseInt ( target , 10 )
target = String ( target )
let found = [ ]
if ( target . startsWith ( '=' ) ) return ( this . cid2char ( target . slice ( 1 ) ) )
if ( target . startsWith ( '<' ) || target . startsWith ( '>' ) ) return ( this . rel2char ( target ) )
if ( ! Number . 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 - 11 ) )
if ( this . in ( int , 21 , 25 ) ) return ( this . pos2char ( int - 16 ) )
found . push ( ... window . ChatRoomCharacter . filter ( c => c . cid . toString ( ) . includes ( target ) ) )
}
if ( target . startsWith ( '@' ) ) target = target . slice ( 1 )
found . push ( ... window . ChatRoomCharacter . filter ( c => c . Name . toLocaleLowerCase ( ) . includes ( target ) ) )
found . push ( ... window . ChatRoomCharacter . filter ( c => c . Nickname && ( c . Nickname . toLocaleLowerCase ( ) . includes ( target ) ) ) ) // eslint-disable-line unicorn/no-array-push-push
const map = { }
for ( const c of found ) {
if ( ! map [ c . cid ] ) map [ c . cid ] = c
}
found = Object . values ( map )
if ( found . length === 0 ) throw new Error ( ` target " ${ input } ": no match ` )
if ( found . length > 1 ) throw new Error ( ` target " ${ input } ": multiple matches ( ${ found . map ( c => ` ${ c . cid } | ${ c . Name } | ${ c . Nickname || c . Name } ` ) . join ( ',' ) } ) ` )
return ( found [ 0 ] )
} ,
char2targets ( char ) {
const [ result , cid ] = [ new Set ( ) , char . cid . toString ( ) ]
result . add ( cid ) . add ( ` = ${ cid } ` )
for ( const t of this . tokenise ( char . Name ) ) {
result . add ( t )
result . add ( ` @ ${ t } ` )
}
if ( char . Nickname ) for ( const t of this . tokenise ( char . Nickname ) ) {
result . add ( t )
result . add ( ` @ ${ t } ` )
}
return result
} ,
donate _data ( target ) {
const char = this . target2char ( target )
if ( char . IsPlayer ( ) ) throw new Error ( 'target must not be you' )
if ( ! char . IsRestrained ( ) ) throw new Error ( 'target must be bound' )
const cost = Math . round ( ( ( Math . random ( ) * 10 ) + 15 ) )
if ( window . Player . Money < cost ) throw new Error ( 'not enough money' )
window . CharacterChangeMoney ( window . Player , - cost )
window . ServerSend ( 'ChatRoomChat' , { Content : 'ReceiveSuitcaseMoney' , Type : 'Hidden' , Target : char . cid } )
2023-12-19 01:15:39 +00:00
window . ChatRoomMessage ( { Sender : window . Player . cid , Type : 'Action' , Content : ` You've bought data for $ ${ cost } and sent it to ${ char . dn } . ` , Dictionary : [ MISSING _PLAYER _DIALOG ] } )
2023-12-17 03:52:43 +00:00
} ,
run _activity ( char , ag , action ) {
try {
if ( ! window . ActivityAllowed ( ) ) throw new Error ( 'activities disabled in this room' )
if ( ! window . ServerChatRoomGetAllowItem ( window . Player , char ) ) throw new Error ( 'no permissions' )
char . FocusGroup = this . ensure ( 'invalid AssetGroup' , ( ) => window . AssetGroupGet ( char . AssetFamily , ag ) )
const activity = this . ensure ( 'invalid activity' , ( ) => window . ActivityAllowedForGroup ( char , char . FocusGroup . Name , true ) . find ( a => a . Name === action || a . Activity ? . Name === action ) )
if ( ( activity . Name || activity . Activity . Name ) . endsWith ( 'Item' ) ) {
const item = this . ensure ( 'no toy found' , ( ) => window . Player . Inventory . find ( i => i . Asset ? . Name === 'SpankingToys' && i . Asset . Group ? . Name === char . FocusGroup . Name && window . AssetSpankingToys . DynamicActivity ( char ) === ( activity . Name || activity . Activity . Name ) ) )
window . DialogPublishAction ( char , item )
} else window . ActivityRun ( window . Player , char , char . FocusGroup , activity )
} finally {
char . FocusGroup = null
}
} ,
replace _me ( match , offset , string ) {
const text = string . slice ( 1 )
let suffix = ' '
if ( text . startsWith ( '\'' ) || text . startsWith ( ' ' ) ) suffix = ''
return ` ${ window . MBCHC . PREF _ACTIVITY } < ${ window . Player . cid } :>SourceCharacter ${ suffix } `
} ,
cid2dict ( type , cid ) {
return ( { Tag : ` ${ type } Character ` , MemberNumber : cid , Text : this . cid2char ( cid ) . dn } )
} ,
send _activity ( message ) {
2023-12-19 01:15:39 +00:00
const dict = [ MISSING _PLAYER _DIALOG ]
2023-12-17 03:52:43 +00:00
const cids = message . match ( this . RE _ACT _CIDS )
if ( cids ) {
message = message . replace ( this . RE _ACT _CIDS , '' )
if ( cids [ 1 ] ) dict . push ( this . cid2dict ( 'Source' , cids [ 1 ] ) )
if ( cids [ 2 ] ) dict . push ( this . cid2dict ( 'Target' , cids [ 2 ] ) , this . cid2dict ( 'Destination' , cids [ 2 ] ) )
}
window . ServerSend ( 'ChatRoomChat' , { Type : 'Action' , Content : message , Dictionary : dict } )
} ,
receive ( data ) {
const char = this . cid2char ( data . Sender )
if ( char . IsPlayer ( ) ) return true // This is our own message, sent back to us
const payload = this . ensure ( 'Empty message' , ( ) => data . Dictionary [ 0 ] )
switch ( payload . type ) {
case 'greetings' : case 'hello' : {
char . MBCHC = payload . value
if ( payload . type === 'greetings' ) this . hello ( char )
break
}
default : // If we don't know the type it may be from a newer version
}
return true
} ,
hello ( char = null ) {
const payload = { type : 'greetings' , value : window . Player . MBCHC }
if ( char ) payload . type = 'hello'
const message = { Content : 'MBCHC' , Type : 'Hidden' , Dictionary : [ payload ] }
if ( char ) message . Target = char . cid
window . ServerSend ( 'ChatRoomChat' , message )
} ,
2023-12-19 01:15:39 +00:00
// disappear() {
// const item = window.InventoryGet(window.Player, 'ItemButt')
// if (!item || !item.Asset || !item.Asset.Name) throw new Error('butt seems empty')
// if (item.Asset.Name !== 'AnalHook') throw new Error('butt seems occupied by something other than the anal hook')
// if (!item.Property.Type || item.Property.Type !== 'Hair') throw new Error('anal hook seems not tied to hair')
// item.Property = {Type: 'Hair', Hide: this.HIDE_ALL}
// window.CharacterRefresh(window.Player, true, true)
// },
// title(title) { // WIP
// if (this.empty(title)) throw new Error('empty title')
// title = this.normalise_message(title, {trim: true, up: true, low: true})
// if (title.length > 16) throw new Error('title too long')
// if (!this.RE_TITLE.test(title)) throw new Error('invalid title')
// window.TitleSet(title)
// // Window.TitleList.push({Name: title, Requirement: () => true}) // check for existing first
// },
2023-12-17 03:52:43 +00:00
copy _fbc _trigger ( trigger ) {
const result = {
Type : 'Action' ,
Event : trigger . Event ,
Matchers : trigger . Matchers . map ( m => ( { Tester : new RegExp ( this . FBC _TESTER _PATCHES . reduce ( ( ax , [ f , r ] ) => ax . replaceAll ( f , r ) , m . Tester . source ) , 'u' ) } ) ) ,
}
return ( result )
} ,
patch _fbc ( ) {
this . remove _fbc _hook ( )
delete this . remove _fbc _hook
window . bce _ActivityTriggers . push ( ... window . bce _ActivityTriggers . filter ( t => t . Type === 'Emote' ) . map ( t => this . copy _fbc _trigger ( t ) ) )
2023-12-19 01:15:39 +00:00
/* (["anim", "pose"]).forEach(tag => {let cmd = window.Commands.find(c => tag === c.Tag); if (cmd) cmd.AutoComplete = this[`complete_fbc_${tag}`]}) */ // this line explodes, because it needs a semicolon in front if it
2023-12-17 03:52:43 +00:00
let cmd = window . Commands . find ( c => c . Tag === 'anim' )
if ( cmd ) cmd . AutoComplete = this . complete _fbc _anim
cmd = window . Commands . find ( c => c . Tag === 'pose' )
if ( cmd ) cmd . AutoComplete = this . complete _fbc _pose
} ,
2023-12-19 01:15:39 +00:00
// gather_versions() {
// return (window.ChatRoomCharacter.filter(c => c.MBCHC).map(c => ({name: c.dn, cid: c.cid, version: c.MBCHC.VERSION})))
// },
2023-12-17 03:52:43 +00:00
find _timezone ( char ) {
const timezones = this . settings ( 'timezones' )
if ( timezones && typeof timezones [ char . cid ] === 'number' ) return ( timezones [ char . cid ] )
const match = ( char . Description ) ? char . Description . match ( this . RE _TZ ) : null
const int = match ? Number . parseInt ( match [ 1 ] + match [ 2 ] , 10 ) : 42
if ( this . in ( int , - 12 , 12 ) ) return ( int )
return ( null )
} ,
player _enters _room ( ) { // Or if the mod is loaded while player is in the room
this . hello ( )
} ,
set _timezone ( args ) {
const tz = Number . parseInt ( args [ 0 ] , 10 )
if ( Number . isNaN ( tz ) ) throw new Error ( ` invalid offset " ${ args [ 0 ] } " ` )
if ( ! this . in ( tz , - 12 , 12 ) ) throw new Error ( 'offset should be [-12,12]' )
const char = this . target2char ( args [ 1 ] )
char . MBCHC _LOCAL . TZ = tz
this . save _settings ( s => {
if ( ! s . timezones ) s . timezones = { }
s . timezones [ char . cid ] = tz
} )
} ,
update _char ( 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 )
} ,
command _mbchc ( argline , cmdline , args ) {
const mbchc = window . MBCHC
try { // `this` is command object
if ( args . length === 0 ) 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 ( '' ) ) )
const cmd = String ( args . shift ( ) )
const sub = mbchc . ensure ( ` unknown subcommand " ${ cmd } " ` , ( ) => mbchc . SUBCOMMANDS _MBCHC [ cmd ] )
sub . cb . call ( mbchc , mbchc , args , argline , cmdline )
} catch ( error ) {
mbchc . report ( error )
}
} ,
command _activity ( argline , cmdline , _ ) {
const mbchc = window . MBCHC
if ( ! mbchc . empty ( argline ) ) {
try { // `this` is command object
const message = mbchc . normalise _message ( cmdline . replace ( mbchc . RE _ACTIVITY , '' ) , { trim : true , dot : true , up : true } )
mbchc . send _activity ( message )
} catch ( error ) {
mbchc . report ( error )
}
}
} ,
command _do ( argline , cmdline , args ) {
const mbchc = window . MBCHC
try { // `this` is command object
if ( args . length === 0 ) 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 ( ', ' ) ) )
let [ verb , zone , target ] = args
const zones = mbchc . ensure ( ` unknown verb " ${ verb } " ` , ( ) => mbchc . DO _DATA . verbs [ verb ] )
if ( Object . keys ( zones ) . length === 1 ) {
if ( ! target ) target = zone
zone = mbchc . MAP _ZONES [ Object . keys ( zones ) [ 0 ] ] [ 0 ]
}
if ( ! zone ) throw new Error ( 'zone missing' )
const ag = mbchc . ensure ( ` unknown zone " ${ zone } " ` , ( ) => mbchc . DO _DATA . zones [ zone ] )
const types = mbchc . ensure ( ` zone " ${ zone } " invalid for " ${ verb } " ` , ( ) => zones [ ag ] )
let char = window . Player
if ( target && ( ( types . self . length === 0 ) || ( types . others . length > 0 ) ) ) char = mbchc . target2char ( target )
const type = char . IsPlayer ( ) ? 'self' : 'others'
const available = window . ActivityAllowedForGroup ( char , ag )
const 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 ) ) )
const actions = mbchc . ensure ( ` zone " ${ zone } " invalid for (" ${ verb } " " ${ type } ") ` , ( ) => types [ type ] )
const action = mbchc . ensure ( ` invalid action ( ${ verb } ${ zone } ${ target } ) ` , ( ) => actions . find ( name => available . find ( a => a . Name === name || a . Activity ? . Name === name ) ) )
mbchc . run _activity ( char , ag , action )
} catch ( error ) {
mbchc . report ( error )
}
} ,
bell ( ) {
setTimeout ( ( ) => {
document . querySelector ( '#InputChat' ) . style . outline = ''
} , 100 )
document . querySelector ( '#InputChat' ) . style . outline = 'solid red'
} ,
complete ( options , space = true ) {
if ( options . length === 0 ) return ( this . bell ( ) )
if ( options . length > 1 ) {
const width = Math . max ( ... options . map ( o => o . length ) )
let pref = null
for ( let i = width ; i > 0 ; i -= 1 ) {
const 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 )
} else window . ElementValue ( 'InputChat' , document . querySelector ( '#InputChat' ) . value . replace ( this . RE _LAST _WORD , ` $ 1 ${ options [ 0 ] } ${ space ? ' ' : '' } ` ) )
} ,
complete _hint ( options ) {
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' ) )
const rescroll = window . ElementIsScrolledToEnd ( 'TextAreaChatLog' )
window . ChatRoomResize ( false )
if ( rescroll ) window . ElementScrollToEnd ( 'TextAreaChatLog' )
} ,
comp _hint _visible ( ) {
return ( this . COMP _HINT . parentElement && this . COMP _HINT . style . display === 'flex' )
} ,
complete _hint _hide ( ) {
if ( ! this . comp _hint _visible ( ) ) return
this . COMP _HINT . style . display = 'none'
window . ChatRoomResize ( false )
} ,
complete _target ( token , me2 = true , check _perms = false ) {
const [ locase , found ] = [ token . toLocaleLowerCase ( ) , new Set ( ) ]
for ( const c of window . ChatRoomCharacter ) {
if ( ( c . IsPlayer ( ) && ! me2 ) || ( check _perms && ! window . ServerChatRoomGetAllowItem ( window . Player , c ) ) ) continue
for ( const s of this . char2targets ( c ) ) {
if ( s . toLocaleLowerCase ( ) . startsWith ( locase ) ) found . add ( s )
}
}
this . complete ( Array . from ( found ) )
} ,
complete _common ( ) {
const input = document . querySelector ( '#InputChat' ) . value
return ( [ this , input , this . tokenise ( input ) ] )
} ,
complete _mbchc ( _args , _locase , _cmdline ) {
const [ mbchc , _input , tokens ] = window . MBCHC . complete _common ( ) ; // `this` is command object
if ( tokens . length === 0 ) return
if ( tokens . length < 2 ) return ( mbchc . complete ( [ ` ${ mbchc . CommandsKey } ${ this . Tag } ` ] ) )
const subname = tokens [ 1 ] . toLocaleLowerCase ( )
if ( tokens . length < 3 ) return ( mbchc . complete ( Object . keys ( mbchc . SUBCOMMANDS _MBCHC ) . filter ( c => c . startsWith ( subname ) ) ) ) // Complete subcommand name
const sub = mbchc . SUBCOMMANDS _MBCHC [ subname ]
if ( sub && sub . args ) {
const argname = Object . keys ( sub . args ) [ tokens . length - 3 ]
if ( argname === 'TARGET' ) return ( mbchc . complete _target ( tokens . at ( - 1 ) , false ) )
if ( argname === '[TARGET]' ) return ( mbchc . complete _target ( tokens . at ( - 1 ) ) , true )
}
} ,
complete _do _target ( actions , token ) {
if ( ! actions ) return
const me2 = ( actions . self . length > 0 )
if ( me2 && actions . others . length === 0 ) return ( this . complete ( [ window . Player . cid . toString ( ) ] ) ) // Target is always the player
this . complete _target ( token , me2 , true )
} ,
complete _do ( _args , _locase , _cmdline ) {
const [ mbchc , _input , tokens ] = window . MBCHC . complete _common ( ) ; // `this` is command object
if ( tokens . length === 0 ) return
if ( tokens . length < 2 ) return ( mbchc . complete ( [ ` ${ mbchc . CommandsKey } ${ this . Tag } ` ] ) )
// 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
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
const ags = mbchc . DO _DATA . verbs [ low ]
if ( ! ags ) return ( mbchc . bell ( ) )
low = tokens [ 2 ] . toLocaleLowerCase ( )
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
const zones = Object . entries ( mbchc . DO _DATA . zones ) . filter ( ( [ zone , ag ] ) => zone . startsWith ( low ) && ags [ ag ] ) . map ( ( [ zone , _ag ] ) => zone )
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
return ( mbchc . complete _do _target ( ags [ mbchc . DO _DATA . zones [ low ] ] , tokens [ 3 ] ) )
}
mbchc . bell ( )
} ,
complete _fbc _anim ( _args , _locase , _cmdline ) {
const [ mbchc , _input , tokens ] = window . MBCHC . complete _common ( ) ; // `this` is command object
if ( tokens . length === 0 ) return
if ( tokens . length < 2 ) return ( mbchc . complete ( [ ` ${ mbchc . CommandsKey } ${ this . Tag } ` ] ) )
if ( tokens . length > 2 ) return ( mbchc . bell ( ) )
const anim = tokens [ 1 ] . toLocaleLowerCase ( )
return ( mbchc . complete ( Object . keys ( window . bce _EventExpressions ) . filter ( a => a . toLocaleLowerCase ( ) . startsWith ( anim ) ) ) )
} ,
complete _fbc _pose ( _args , _locase , _cmdline ) {
const [ mbchc , _input , tokens ] = window . MBCHC . complete _common ( ) ; // `this` is command object
if ( tokens . length === 0 ) return
if ( tokens . length < 2 ) return ( mbchc . complete ( [ ` ${ mbchc . CommandsKey } ${ this . Tag } ` ] ) )
const pose = tokens . at ( - 1 ) . toLocaleLowerCase ( )
return ( mbchc . complete ( window . PoseFemale3DCG . map ( p => p . Name ) . filter ( p => p . toLocaleLowerCase ( ) . startsWith ( pose ) ) ) )
} ,
history ( down ) {
const [ text , history ] = [ window . ElementValue ( 'InputChat' ) , window . ChatRoomLastMessage ]
if ( ! this . HISTORY _MODE ) {
history . push ( text )
this . HISTORY _MODE = true
}
const ids = history . map ( ( t , i ) => [ t , i ] ) . filter ( ( [ t , _ ] ) => t . startsWith ( history . at ( - 1 ) ) ) . map ( ( [ _ , i ] ) => i )
if ( ! down ) ids . reverse ( )
const found = ids . find ( id => ( down ) ? id > window . ChatRoomLastMessageIndex : id < window . ChatRoomLastMessageIndex )
if ( ! found ) return ( this . bell ( ) )
window . ElementValue ( 'InputChat' , history [ found ] )
window . ChatRoomLastMessageIndex = found
} ,
2023-12-19 01:15:39 +00:00
focus _chat _checks ( ) { // we only want to catch chatlog and canvas (no map though) keypresses
if ( document . activeElement === document . body ) return true
if ( document . activeElement . id !== 'MainCanvas' ) return false
return ! window . ChatRoomMapVisible
} ,
2023-12-17 03:52:43 +00:00
focus _chat _whitelist ( event ) {
if ( event . ctrlKey && event . key === 'v' ) return true // Ctrl+V should paste
return false
} ,
2023-12-19 01:15:39 +00:00
focus _chat ( event ) {
2023-12-17 03:52:43 +00:00
if ( event . repeat ) return // Only unique presses please
2023-12-19 01:15:39 +00:00
if ( ! this . focus _chat _checks ( ) ) return
2023-12-17 03:52:43 +00:00
if ( [ event . altKey , event . ctrlKey , event . metaKey ] . some ( Boolean ) && ! this . focus _chat _whitelist ( event ) ) return // Alt, ctrl and meta should all be false
2023-12-19 01:15:39 +00:00
if ( document . querySelector ( '#InputChat' ) ? . style . display !== 'inline' ) return // Input chat missing
2023-12-17 03:52:43 +00:00
window . ElementFocus ( 'InputChat' )
} ,
loader ( ) {
if ( this . remove _load _hook ) {
this . remove _load _hook ( )
delete this . remove _load _hook
}
if ( this . LOADED ) return
// Calculated values
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 } ,
]
2023-12-19 01:15:39 +00:00
// this.HIDE_ALL = this.HIDE_SPECIAL.concat(this.HIDE_BODY).concat(this.HIDE_CLOTHES).concat(this.HIDE_ITEMS)
2023-12-17 03:52:43 +00:00
this . CommandsKey = CommandsKey /* eslint-disable-line no-undef */ // window.CommandsKey is undefined
this . RE _ACTIVITY = new RegExp ( ` ^ ${ this . CommandsKey } activity ` )
this . PREF _ACTIVITY = ` ${ this . CommandsKey } activity `
this . COMP _HINT = document . createElement ( 'div' )
this . COMP _HINT . id = 'mbchcCompHint'
const css = document . createElement ( 'style' )
css . 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.5 ex ;
}
`
document . head . append ( css )
// Actions
this . calculate _maps ( )
window . Player . MBCHC = { VERSION : this . VERSION }
window . CommandCombine ( COMMANDS )
// Hooks
this . remove _fbc _hook = this . before ( 'MainRun' , ( ) => window . bce _ActivityTriggers && this . patch _fbc ( ) )
this . after ( 'CharacterOnlineRefresh' , char => this . update _char ( char ) )
this . after ( 'ChatRoomReceiveSuitcaseMoney' , ( ) => {
if ( this . AUTOHACK _ENABLED && this . LAST _HACKED ) {
window . CurrentCharacter = this . cid2char ( this . LAST _HACKED )
this . LAST _HACKED = null
window . ChatRoomTryToTakeSuitcase ( )
}
} )
this . before ( 'ChatRoomSendChat' , ( ) => {
let input = window . ElementValue ( 'InputChat' )
if ( ! input . startsWith ( '@@@' ) && input . startsWith ( '@' ) ) {
input = input . replace ( this . RE _PREF _ACTIVITY , this . PREF _ACTIVITY )
input = input . replace ( this . RE _PREF _ACTIVITY _ME , this . replace _me )
window . ElementValue ( 'InputChat' , input )
}
} )
this . after ( 'ChatRoomSendChat' , ( ) => {
const history = window . ChatRoomLastMessage
if ( ( history . length > 1 ) && ( history . at ( - 1 ) === history . at ( - 2 ) ) ) {
history . pop ( )
window . ChatRoomLastMessageIndex -= 1
}
} )
this . before ( 'ChatRoomDrawCharacterOverlay' , ( C , CharX , CharY , Zoom , _Pos ) => {
2023-12-19 01:15:39 +00:00
// if (window.ChatRoomHideIconState < 1 && C.MBCHC) {
// window.DrawRect(CharX + (175 * Zoom), CharY, 50 * Zoom, 50 * Zoom, C.MBCHC.VERSION === window.Player.MBCHC.VERSION ? this.RGB_POLLY : this.RGB_MUTE)
// }
2023-12-17 03:52:43 +00:00
if ( window . ChatRoomHideIconState < 1 && C . MBCHC _LOCAL && typeof C . MBCHC _LOCAL . TZ === 'number' ) {
const hours = new Date ( window . CommonTime ( ) + this . UTC _OFFSET + ( C . MBCHC _LOCAL . TZ * 60 * 60 * 1000 ) ) . getHours ( )
window . DrawTextFit ( hours < 10 ? '0' + hours . toString ( ) : hours . toString ( ) , CharX + ( 200 * Zoom ) , CharY + ( 25 * Zoom ) , 46 * Zoom , 'white' , 'black' )
}
} )
this . after ( 'ElementValue' , ( ID , Value , cc = window . CurrentCharacter ) => ID === 'bce_LayerPriority' && cc ? . FocusGroup && window . InventoryGet ( cc , cc . FocusGroup . Name ) ? . Difficulty . toString ( ) === Value && window . InventoryLocked ( cc , cc . FocusGroup . Name , true ) && window . ElementSetAttribute ( ID , 'disabled' , true ) )
this . after ( 'ChatRoomCreateElement' , ( ) => this . COMP _HINT . parentElement || document . body . append ( this . COMP _HINT ) )
this . before ( 'ChatRoomClearAllElements' , ( ) => {
this . complete _hint _hide ( )
this . COMP _HINT . remove ( )
} )
this . before ( 'ChatRoomClick' , ( ) => this . complete _hint _hide ( ) )
this . after ( 'ChatRoomResize' , ( ) => {
if ( window . CharacterGetCurrent ( ) === null && window . CurrentScreen === 'ChatRoom' && document . querySelector ( '#InputChat' ) && document . querySelector ( '#TextAreaChatLog' ) && this . comp _hint _visible ( ) ) { // Upstream
const fontsize = ChatRoomFontSize /* eslint-disable-line no-undef */ // window.ChatRoomFontSize is undefined
window . ElementPositionFix ( 'TextAreaChatLog' , fontsize , 1005 , 66 , 988 , 630 )
window . ElementPositionFix ( this . COMP _HINT . id , fontsize , 1005 , 701 , 988 , 200 )
this . COMP _HINT . style . display = 'flex'
}
} )
document . addEventListener ( 'keydown' , event => this . focus _chat ( event ) )
this . SDK . hookFunction ( 'ChatRoomKeyDown' , 0 , ( nextargs , next ) => { // This fires on chat input events
const [ event ] = nextargs
window . MBCHC . complete _hint _hide ( )
if ( ( window . KeyPress === 33 ) || ( window . KeyPress === 34 ) ) { // Better history
event . preventDefault ( )
return ( window . MBCHC . history ( window . KeyPress - 33 ) )
}
if ( window . MBCHC . HISTORY _MODE ) {
window . ChatRoomLastMessage . pop ( )
window . MBCHC . HISTORY _MODE = false
}
return ( next ( nextargs ) )
} )
// Chat room handlers
window . ChatRoomRegisterMessageHandler ( { Priority : - 220 , Description : 'MBCHC preprocessor' , Callback : ( data , sender , message , metadata ) => {
data . MBCHC _ID = this . NEXT _MESSAGE
this . NEXT _MESSAGE += 1
if ( this . LOG _MESSAGES ) console . debug ( { data , sender , msg : message , metadata } )
} } )
window . ChatRoomRegisterMessageHandler ( { Priority : - 219 , Description : 'MBCHC room enter hook' ,
Callback : ( data , _sender , _message , _metadata ) => {
if ( ( data . Type === 'Action' ) && ( data . Content === 'ServerEnter' ) && ( data . Sender === window . Player . cid ) ) this . player _enters _room ( )
} ,
} )
window . ChatRoomRegisterMessageHandler ( { Priority : - 219 , Description : 'MBCHC specific consumer' ,
Callback : ( data , _sender , _message , _metadata ) => {
if ( ( data . Type === 'Hidden' ) && ( data . Content === 'MBCHC' ) ) return this . receive ( data )
} ,
} )
window . ChatRoomRegisterMessageHandler ( { Priority : - 219 , Description : 'MBCHC autohack lookup' ,
Callback : ( data , _sender , _message , _metadata ) => {
if ( ( data . Type === 'Hidden' ) && ( data . Content === 'ReceiveSuitcaseMoney' ) ) this . LAST _HACKED = data . Sender
} ,
} )
// Footer
this . LOADED = true
this . log ( 'info' , ` loaded version ${ this . VERSION } ` )
if ( ( window . CurrentModule === 'Online' ) && ( window . CurrentScreen === 'ChatRoom' ) ) {
for ( const c of window . ChatRoomCharacter ) this . update _char ( c )
this . player _enters _room ( )
}
} ,
preloader ( ) {
2023-12-19 01:15:39 +00:00
if ( ! window . AsylumGGTSSAddItems ) throw new Error ( 'AsylumGGTSSAddItems() not found, aborting MBCHC loading' )
if ( ! window . bcModSdk ) throw new Error ( 'SDK not found, please load with (or after) FUSAM' )
2023-12-17 03:52:43 +00:00
this . SDK = window . bcModSdk . registerMod ( { name : 'MBCHC' , fullName : 'Mute\'s Bondage Club Hacks Collection' , version : this . VERSION , repository : 'https://code.fleshless.org/mute/MBCHC/' } )
this . before = ( name , cb ) => this . SDK . hookFunction ( name , 0 , ( nextargs , next ) => {
try {
cb ? . ( ... nextargs )
} catch ( error ) {
console . error ( error )
}
return next ( nextargs )
} )
this . after = ( name , cb ) => this . SDK . hookFunction ( name , 0 , ( nextargs , next ) => {
const result = next ( nextargs ) ;
try {
cb ? . ( ... nextargs )
} catch ( error ) {
console . error ( error )
}
return result
} )
if ( window . CurrentModule && window . CurrentScreen && ! ( window . CurrentModule === 'Character' && window . CurrentScreen === 'Login' ) ) return this . loader ( )
this . remove _load _hook = this . before ( 'AsylumGGTSSAddItems' , ( ) => this . loader ( ) )
} ,
}
window . MBCHC . preloader ( )
2023-12-19 01:15:39 +00:00
// TODO: with the removal of /mbchc versions it makes no sense to keep track of the mod version and the whole hello() infrastructure can go away