105.13.0 semver; settings migration
[lots of internal change] I'm determined to clean this mess up after all. Instead of a total rewrite (which didn't work), I'll migrate the code to JSDoc piece by piece. This is the first piece, setting up some basic framework and testing out this and that. Until I figure out how test this thing, I'll just ask peeps to import the module from testing branch I guess. Probably not gonna deal with anything new until I can turn "strict" on.
This commit is contained in:
parent
90231cb2ae
commit
16308eccf1
62
ambient.d.ts
vendored
Normal file
62
ambient.d.ts
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
interface ServerChatRoomMessage {
|
||||
MBCHC_ID?: number
|
||||
}
|
||||
|
||||
declare namespace MBCHC {
|
||||
type WGT = Window & typeof globalThis
|
||||
interface Root extends WGT {
|
||||
MBCHC?: any
|
||||
bcModSdk?: import('bondage-club-mod-sdk').ModSDKGlobalAPI
|
||||
bce_ActivityTriggers?: any
|
||||
bce_EventExpressions?: any
|
||||
}
|
||||
|
||||
namespace Settings {
|
||||
type V0 = PlayerOnlineSettings & {MBCHC: {timezones?: Record<number, number>}} // V0 is the whole onlinesettings
|
||||
interface V1 { // V1 is specifically MBCHC inside extensionsettings
|
||||
TZ: Record<number, number>
|
||||
}
|
||||
interface Methods {
|
||||
migrate_0_1(v0: V0): true
|
||||
save(cb?: (v1: V1) => unknown): true
|
||||
replace(new_v1: V1): true
|
||||
'purge!'(): true
|
||||
get v1(): V1
|
||||
}
|
||||
}
|
||||
|
||||
interface Interval {
|
||||
proxy: object // eslint-disable-line @typescript-eslint/ban-types
|
||||
min: number
|
||||
max: number
|
||||
mini: boolean
|
||||
maxi: boolean
|
||||
upd(min: number, max: number, min_inclusive?: boolean, max_inclusive?: boolean): undefined
|
||||
has(_: unknown, x: string): boolean
|
||||
}
|
||||
|
||||
interface Utils {
|
||||
interval: Interval
|
||||
cid(character: Character): number | undefined
|
||||
dn(character: Character): string
|
||||
current(): string
|
||||
with<V, R>(value: V, cb: (value: V) => R): R // A silly helper to kinda curry values
|
||||
true<F extends (...args: unknown[]) => unknown>(cb: F): true // Useful for type-safe chaining
|
||||
mutate<T>(value: T, cb: (v: T) => unknown): T // A silly helper for chaining
|
||||
rm<T>(object: T, property: keyof T): T
|
||||
mrg<T extends Record<string, unknown>>(target: T, ...source: T[]): T // Shorter, also less confusing with types
|
||||
style<T>(query: string, cb: (s: CSSStyleDeclaration) => T): T | undefined
|
||||
range(min: number, max: number, min_inclusive?: boolean, max_inclusive?: boolean): Proxy
|
||||
assert<T>(error: string, value: T, cb?: (value: T) => boolean): T | never
|
||||
each<T extends Record<string, unknown>>(object: T, cb: (key: string, value: unknown) => unknown): true
|
||||
}
|
||||
|
||||
interface TZ_Cache {
|
||||
map: Map<number, number>
|
||||
RE: RegExp
|
||||
for(character: Character): number | undefined
|
||||
memo(member_number: number, description?: string | undefined): number | undefined
|
||||
parse(description: string | undefined): number | undefined
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"include": [
|
||||
"node_modules/bc-stubs/bc/**/*.d.ts",
|
||||
"node_modules/bondage-club-mod-sdk/dist/**/*.d.ts",
|
||||
"typedef.d.ts",
|
||||
"ambient.d.ts",
|
||||
"mbchc.mjs",
|
||||
"server.js"
|
||||
],
|
||||
|
@ -13,6 +13,7 @@
|
|||
],
|
||||
"checkJs": true,
|
||||
"strict": false,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitOverride": true
|
||||
}
|
||||
}
|
||||
|
|
376
mbchc.mjs
376
mbchc.mjs
|
@ -1,45 +1,57 @@
|
|||
export {}
|
||||
/** @type {MBCHC.Root} */ const W = window, D = W.document
|
||||
if (W.MBCHC !== undefined) throw new Error('MBCHC found, aborting loading')
|
||||
|
||||
/** @typedef {import('bondage-club-mod-sdk').ModSDKGlobalAPI} ModSDKGlobalAPI */
|
||||
export const VERSION = '105.13.0'
|
||||
const MISSING_PLAYER_DIALOG = {Tag: 'MISSING TEXT IN "Interface.csv": ', Text: '\u200C'} // Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsey value
|
||||
|
||||
/** @type {Window & typeof globalThis & {MBCHC?: any, bcModSdk?: ModSDKGlobalAPI, bce_ActivityTriggers?: any, bce_EventExpressions?: any}} */
|
||||
const w = window
|
||||
/** @implements {MBCHC.Interval} */ class Interval { proxy = new Proxy({}, this); min = 0; max = 0; mini = false; maxi = false
|
||||
/** @type {MBCHC.Interval['upd']} */ upd(min, max, mini = true, maxi = true) {this.min = min; this.max = max; this.mini = mini; this.maxi = maxi}
|
||||
/** @type {MBCHC.Interval['has']} */ has(_, x) {return U.with(Number(x), x => (this.mini ? x >= this.min : x > this.min) && (this.maxi ? x <= this.max : x < this.max))}
|
||||
}
|
||||
|
||||
/**
|
||||
* A silly helper to memorise values in callbacks
|
||||
* @template V, R
|
||||
* @param {V} v Value to memorise
|
||||
* @param {function(V): R} cb Callback
|
||||
* @returns {R} Return value of the callback
|
||||
*/
|
||||
const take = (v, cb) => cb(v)
|
||||
/** @type {MBCHC.Utils} */ const U = { interval: new Interval(),
|
||||
cid: char => char.MemberNumber,
|
||||
dn: char => W.CharacterNickname(char),
|
||||
current: () => `${W.CurrentModule}/${W.CurrentScreen}`,
|
||||
with: (v, cb) => cb(v),
|
||||
true(cb) {cb(); return true},
|
||||
mutate(v, cb) {cb(v); return v},
|
||||
rm(o, p) {delete o[p]; return o}, // eslint-disable-line @typescript-eslint/no-dynamic-delete
|
||||
mrg: (t, ...s) => Object.assign(t, ...s), // eslint-disable-line @typescript-eslint/no-unsafe-return
|
||||
style: (q, cb) => U.with(D.querySelector(q), E => E instanceof HTMLElement ? cb(E.style) : undefined),
|
||||
range: (...args) => U.true(() => void U.interval.upd(...args)) && U.interval.proxy,
|
||||
assert(x, v, cb = Boolean) {if (!cb(v)) throw new Error(x); return v},
|
||||
each: (o, cb) => U.true(() => void Object.entries(o).forEach(kv => cb(...kv))),
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a DOM query and passes the element's style into a callback, returning its result
|
||||
* @template T
|
||||
* @param {string} q Query
|
||||
* @param {function(CSSStyleDeclaration): T} cb Callback
|
||||
* @returns {T | void} Return value of the callback, if it was called
|
||||
*/
|
||||
const style = (q, cb) => take(document.querySelector(q), E => E && E instanceof HTMLElement && E.style ? cb(E.style) : undefined)
|
||||
/** @type {MBCHC.Settings.Methods} */ const Settings = {
|
||||
migrate_0_1(v0) { // I hate change
|
||||
U.with(v0.MBCHC.timezones, tz => tz !== undefined && this.save(v1 => void U.each(tz, (k, v) => v1.TZ[k] ||= v)))
|
||||
W.ServerAccountUpdate.QueueData({OnlineSettings: U.rm(v0, 'MBCHC')})
|
||||
return U.true(() => void console.warn('MBCHC: settings migration done (v0 -> v1). This should never appear again.'))
|
||||
},
|
||||
save(cb = undefined) {W.Player.ExtensionSettings.MBCHC ||= {}; cb?.(this.v1); W.ServerPlayerExtensionSettingsSync('MBCHC'); return true},
|
||||
replace: v1 => U.true(() => W.Player.ExtensionSettings.MBCHC = v1) && Settings.save(),
|
||||
'purge!': () => Settings.replace(/** @type {MBCHC.Settings.V1} */ ({})),
|
||||
get v1() {return U.mutate(/** @type {MBCHC.Settings.V1} */ (W.Player.ExtensionSettings.MBCHC) ?? {}, v1 => { // we need to check and repair the whole object every time we access it
|
||||
v1.TZ ||= {}
|
||||
})},
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} Current view
|
||||
*/
|
||||
const current = () => `${w.CurrentModule}/${w.CurrentScreen}`
|
||||
/** @type {MBCHC.TZ_Cache} */ const TZ = { map: new Map(),
|
||||
RE: /(?:gmt|utc)\s*([+-])\s*(\d\d?)/i,
|
||||
for: c => c.MemberNumber === undefined ? undefined : TZ.map.get(c.MemberNumber) ?? TZ.memo(c.MemberNumber, c.Description),
|
||||
memo: (cid, desc = undefined) => U.with(Settings.v1.TZ[cid] ?? TZ.parse(desc), n => n === undefined ? undefined : U.true(() => TZ.map.set(cid, n)) && n),
|
||||
parse: desc => desc === undefined ? undefined : U.with(TZ.RE.exec(desc), m => m === null ? undefined : U.with(Number.parseInt(m[1] + m[2], 10), n => n in U.range(-12, 12) ? n : undefined)),
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Character} char
|
||||
* @returns {number}
|
||||
*/
|
||||
const cid = char => char.MemberNumber
|
||||
// ^ type-safe (still need strict later)
|
||||
// =================================================================================
|
||||
// v legacy mess
|
||||
|
||||
// Zero-width non-joiner, used to break up ligatures, does nothing here, but an empty string is a falsey value
|
||||
const MISSING_PLAYER_DIALOG = {Tag: 'MISSING TEXT IN "Interface.csv": ', Text: '\u200C'}
|
||||
|
||||
if (w.MBCHC) throw new Error('MBCHC found, aborting loading')
|
||||
w.MBCHC = {
|
||||
VERSION: 'dev.12',
|
||||
W.MBCHC = {
|
||||
VERSION,
|
||||
Settings,
|
||||
NEXT_MESSAGE: 1,
|
||||
LOG_MESSAGES: false,
|
||||
RETHROW: false,
|
||||
|
@ -50,7 +62,6 @@ w.MBCHC = {
|
|||
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,
|
||||
|
@ -163,17 +174,12 @@ w.MBCHC = {
|
|||
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
|
||||
donate: {desc: 'Buy data and send it to recipient', args: {TARGET: {}}, cb: (mbchc, args) => mbchc.donate_data(args[0])},
|
||||
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 (w.Player.OnlineSettings.MBCHC) {
|
||||
delete w.Player.OnlineSettings.MBCHC
|
||||
mbchc.save_settings()
|
||||
}
|
||||
}},
|
||||
},
|
||||
ensure(text, callback) {
|
||||
const result = callback.call(this)
|
||||
if (!result) throw new Error(text)
|
||||
return (result)
|
||||
'purge!': {desc: 'delete MBCHC online saved data', cb: () => Settings['purge!']()},
|
||||
// if (W.Player.ExtensionSettings.MBCHC) {
|
||||
// delete W.Player.ExtensionSettings.MBCHC
|
||||
// mbchc.save_settings() // FIXME
|
||||
// }
|
||||
//}},
|
||||
},
|
||||
calculate_maps() {
|
||||
this.DO_DATA = {verbs: {}, zones: {}}
|
||||
|
@ -188,17 +194,6 @@ w.MBCHC = {
|
|||
}
|
||||
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 = w.Player.OnlineSettings.MBCHC || {}
|
||||
return (setting ? settings[setting] : settings)
|
||||
},
|
||||
save_settings(cb = null) {
|
||||
if (cb) {
|
||||
if (!w.Player.OnlineSettings.MBCHC) w.Player.OnlineSettings.MBCHC = {}
|
||||
cb.call(this, w.Player.OnlineSettings.MBCHC)
|
||||
}
|
||||
w.ServerAccountUpdate.QueueData({OnlineSettings: w.Player.OnlineSettings})
|
||||
},
|
||||
log(level, message) {
|
||||
console[level]('MBCHC: ' + String(message))
|
||||
},
|
||||
|
@ -223,64 +218,61 @@ w.MBCHC = {
|
|||
return text.replace(this.RE_SPACES, ' ').split(' ')
|
||||
},
|
||||
inform(html, timeout = 60_000) {
|
||||
w.ChatRoomSendLocal(`<div class="mbchc">${html}</div>`, timeout)
|
||||
W.ChatRoomSendLocal(`<div class="mbchc">${html}</div>`, timeout)
|
||||
},
|
||||
report(x) {
|
||||
this.inform(`${x.toString()}`)
|
||||
if (this.RETHROW) throw x
|
||||
},
|
||||
in(x, floor, ceiling) {
|
||||
return ((x >= floor) && (x <= ceiling))
|
||||
},
|
||||
cid2char(id) {
|
||||
id = Number.parseInt(id, 10)
|
||||
if (id === cid(w.Player)) return (w.Player)
|
||||
return (this.ensure(`character ${id} not found in the room`, () => w.ChatRoomCharacter.find(c => cid(c) === id)))
|
||||
if (id === U.cid(W.Player)) return (W.Player)
|
||||
return U.assert(`character ${id} not found in the room`, W.ChatRoomCharacter.find(c => U.cid(c) === id))
|
||||
},
|
||||
pos2char(pos) {
|
||||
if (!this.in(pos, 0, w.ChatRoomCharacter.length - 1)) throw new Error(`invalid position ${pos}`)
|
||||
return (w.ChatRoomCharacter[pos])
|
||||
if (!(pos in U.range(0, W.ChatRoomCharacter.length - 1))) throw new Error(`invalid position ${pos}`)
|
||||
return (W.ChatRoomCharacter[pos])
|
||||
},
|
||||
rel2char(target) {
|
||||
const me = this.ensure('can\'t find my position', () => w.ChatRoomCharacter.findIndex(char => char.IsPlayer()) + 1) - 1 // 0 is falsey, but is valid index
|
||||
const me = U.assert('can\'t find my position', W.ChatRoomCharacter.findIndex(char => char.IsPlayer()) + 1) - 1 // 0 is falsey, 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 %= w.ChatRoomCharacter.length
|
||||
if (pos < 0) pos += w.ChatRoomCharacter.length
|
||||
pos %= W.ChatRoomCharacter.length
|
||||
if (pos < 0) pos += W.ChatRoomCharacter.length
|
||||
return (this.pos2char(pos))
|
||||
},
|
||||
target2char(target) { // Target should be lowcase
|
||||
const input = target
|
||||
if (this.empty(target)) return (w.Player)
|
||||
if (this.empty(target)) return (W.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(...w.ChatRoomCharacter.filter(c => cid(c).toString().includes(target)))
|
||||
if (int in U.range(0, 9)) return (this.pos2char(int))
|
||||
if (int in U.range(11, 15)) return (this.pos2char(int - 11))
|
||||
if (int in U.range(21, 25)) return (this.pos2char(int - 16))
|
||||
found.push(...W.ChatRoomCharacter.filter(c => U.cid(c).toString().includes(target)))
|
||||
}
|
||||
|
||||
if (target.startsWith('@')) target = target.slice(1)
|
||||
found.push(...w.ChatRoomCharacter.filter(c => c.Name.toLocaleLowerCase().includes(target)))
|
||||
found.push(...w.ChatRoomCharacter.filter(c => c.Nickname && (c.Nickname.toLocaleLowerCase().includes(target)))) // eslint-disable-line unicorn/no-array-push-push
|
||||
found.push(...W.ChatRoomCharacter.filter(c => c.Name.toLocaleLowerCase().includes(target)))
|
||||
found.push(...W.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[cid(c)]) map[cid(c)] = c
|
||||
if (!map[U.cid(c)]) map[U.cid(c)] = 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 => `${cid(c)}|${c.Name}|${c.Nickname || c.Name}`).join(',')})`)
|
||||
if (found.length > 1) throw new Error(`target "${input}": multiple matches (${found.map(c => `${U.cid(c)}|${c.Name}|${c.Nickname || c.Name}`).join(',')})`)
|
||||
return (found[0])
|
||||
},
|
||||
char2targets(char) {
|
||||
const [result, id] = [new Set(), cid(char).toString()]
|
||||
const [result, id] = [new Set(), U.cid(char).toString()]
|
||||
result.add(id).add(`=${id}`)
|
||||
for (const t of this.tokenise(char.Name)) {
|
||||
result.add(t)
|
||||
|
@ -294,27 +286,28 @@ w.MBCHC = {
|
|||
|
||||
return result
|
||||
},
|
||||
//get settings() {return Settings.v1},
|
||||
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 (w.Player.Money < cost) throw new Error('not enough money')
|
||||
w.CharacterChangeMoney(w.Player, -cost)
|
||||
w.ServerSend('ChatRoomChat', {Content: 'ReceiveSuitcaseMoney', Type: 'Hidden', Target: cid(char)})
|
||||
w.ChatRoomMessage({Sender: cid(w.Player), Type: 'Action', Content: `You've bought data for $${cost} and sent it to ${char.dn}.`, Dictionary: [MISSING_PLAYER_DIALOG]})
|
||||
if (W.Player.Money < cost) throw new Error('not enough money')
|
||||
W.CharacterChangeMoney(W.Player, -cost)
|
||||
W.ServerSend('ChatRoomChat', {Content: 'ReceiveSuitcaseMoney', Type: 'Hidden', Target: U.cid(char)})
|
||||
W.ChatRoomMessage({Sender: U.cid(W.Player), Type: 'Action', Content: `You've bought data for $${cost} and sent it to ${U.dn(char)}.`, Dictionary: [MISSING_PLAYER_DIALOG]})
|
||||
},
|
||||
run_activity(char, ag, action) {
|
||||
try {
|
||||
if (!w.ActivityAllowed()) throw new Error('activities disabled in this room')
|
||||
if (!w.ServerChatRoomGetAllowItem(w.Player, char)) throw new Error('no permissions')
|
||||
char.FocusGroup = this.ensure('invalid AssetGroup', () => w.AssetGroupGet(char.AssetFamily, ag))
|
||||
const activity = this.ensure('invalid activity', () => w.ActivityAllowedForGroup(char, char.FocusGroup.Name).find(a => a.Activity?.Name === action))
|
||||
if (!W.ActivityAllowed()) throw new Error('activities disabled in this room')
|
||||
if (!W.ServerChatRoomGetAllowItem(W.Player, char)) throw new Error('no permissions')
|
||||
char.FocusGroup = U.assert('invalid AssetGroup', W.AssetGroupGet(char.AssetFamily, ag))
|
||||
const activity = U.assert('invalid activity', W.ActivityAllowedForGroup(char, char.FocusGroup.Name).find(a => a.Activity?.Name === action))
|
||||
//if ((activity.Name || activity.Activity.Name).endsWith('Item')) {
|
||||
// const item = this.ensure('no toy found', () => w.Player.Inventory.find(i => i.Asset?.Name === 'SpankingToys' && i.Asset.Group?.Name === char.FocusGroup.Name && w.AssetSpankingToys.DynamicActivity(char) === (activity.Name || activity.Activity.Name)))
|
||||
// w.DialogPublishAction(char, item)
|
||||
//} else w.ActivityRun(w.Player, char, char.FocusGroup, activity)
|
||||
w.ActivityRun(w.Player, char, char.FocusGroup, activity)
|
||||
W.ActivityRun(W.Player, char, char.FocusGroup, activity)
|
||||
} finally {
|
||||
char.FocusGroup = null
|
||||
}
|
||||
|
@ -323,10 +316,10 @@ w.MBCHC = {
|
|||
const text = string.slice(1)
|
||||
let suffix = ' '
|
||||
if (text.startsWith('\'') || text.startsWith(' ')) suffix = ''
|
||||
return `${w.MBCHC.PREF_ACTIVITY}<${cid(w.Player)}:>SourceCharacter${suffix}`
|
||||
return `${W.MBCHC.PREF_ACTIVITY}<${U.cid(W.Player)}:>SourceCharacter${suffix}`
|
||||
},
|
||||
cid2dict(type, cid) {
|
||||
return ({Tag: `${type}Character`, MemberNumber: cid, Text: this.cid2char(cid).dn})
|
||||
return ({Tag: `${type}Character`, MemberNumber: cid, Text: U.dn(this.cid2char(cid))})
|
||||
},
|
||||
send_activity(message) {
|
||||
const dict = [MISSING_PLAYER_DIALOG]
|
||||
|
@ -336,7 +329,7 @@ w.MBCHC = {
|
|||
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]))
|
||||
}
|
||||
w.ServerSend('ChatRoomChat', {Type: 'Action', Content: message, Dictionary: dict})
|
||||
W.ServerSend('ChatRoomChat', {Type: 'Action', Content: message, Dictionary: dict})
|
||||
},
|
||||
//receive(data) {
|
||||
// const char = this.cid2char(data.Sender)
|
||||
|
@ -370,54 +363,43 @@ w.MBCHC = {
|
|||
patch_fbc() {
|
||||
this.remove_fbc_hook()
|
||||
delete this.remove_fbc_hook
|
||||
w.bce_ActivityTriggers.push(...w.bce_ActivityTriggers.filter(t => t.Type === 'Emote').map(t => this.copy_fbc_trigger(t)))
|
||||
W.bce_ActivityTriggers.push(...W.bce_ActivityTriggers.filter(t => t.Type === 'Emote').map(t => this.copy_fbc_trigger(t)))
|
||||
/* (["anim", "pose"]).forEach(tag => {let cmd = w.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
|
||||
let cmd = w.Commands.find(c => c.Tag === 'anim')
|
||||
let cmd = W.Commands.find(c => c.Tag === 'anim')
|
||||
if (cmd) cmd.AutoComplete = this.complete_fbc_anim
|
||||
cmd = w.Commands.find(c => c.Tag === 'pose')
|
||||
cmd = W.Commands.find(c => c.Tag === 'pose')
|
||||
if (cmd) cmd.AutoComplete = this.complete_fbc_pose
|
||||
},
|
||||
find_timezone(char) {
|
||||
const timezones = this.settings('timezones')
|
||||
if (timezones && typeof timezones[cid(char)] === 'number') return (timezones[cid(char)])
|
||||
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]')
|
||||
if (!(tz in U.range(-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[cid(char)] = tz
|
||||
})
|
||||
},
|
||||
update_char(char) {
|
||||
//char.cid = char.MemberNumber // Club ID (shorter)
|
||||
char.dn = w.CharacterNickname(char) // DisplayName (shortcut)
|
||||
if (!char.MBCHC_LOCAL) char.MBCHC_LOCAL = {}
|
||||
if (!char.MBCHC_LOCAL.TZ) char.MBCHC_LOCAL.TZ = this.find_timezone(char)
|
||||
if (char.MemberNumber === undefined) return
|
||||
Settings.save(v1 => v1.TZ[char.MemberNumber] = tz)
|
||||
TZ.memo(char.MemberNumber)
|
||||
//char.MBCHC_LOCAL.TZ = tz
|
||||
//this.save_settings(s => { // FIXME
|
||||
// if (!s.timezones) s.timezones = {}
|
||||
// s.timezones[U.cid(char)] = tz
|
||||
//})
|
||||
},
|
||||
command_mbchc(argline, cmdline, args) {
|
||||
const mbchc = w.MBCHC
|
||||
const mbchc = W.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])
|
||||
const sub = U.assert(`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 = w.MBCHC
|
||||
const mbchc = W.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})
|
||||
|
@ -428,27 +410,27 @@ w.MBCHC = {
|
|||
}
|
||||
},
|
||||
command_do(argline, cmdline, args) {
|
||||
const mbchc = w.MBCHC
|
||||
const mbchc = W.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])
|
||||
const zones = U.assert(`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 = w.Player
|
||||
const ag = U.assert(`unknown zone "${zone}"`, mbchc.DO_DATA.zones[zone])
|
||||
const types = U.assert(`zone "${zone}" invalid for "${verb}"`, zones[ag])
|
||||
let char = W.Player
|
||||
if (target && ((types.self.length === 0) || (types.others.length > 0))) char = mbchc.target2char(target)
|
||||
const type = char.IsPlayer() ? 'self' : 'others'
|
||||
const available = w.ActivityAllowedForGroup(char, ag)
|
||||
const available = W.ActivityAllowedForGroup(char, ag)
|
||||
//const toy = w.InventoryGet(w.Player, 'ItemHands')
|
||||
//if (toy && toy.Asset.Name === 'SpankingToys') available.push(w.AssetAllActivities(char.AssetFamily).find(a => a.Name === w.InventorySpankingToysGetActivity?.(w.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.Activity?.Name === name)))
|
||||
const actions = U.assert(`zone "${zone}" invalid for ("${verb}" "${type}")`, types[type])
|
||||
const action = U.assert(`invalid action (${verb} ${zone} ${target})`, actions.find(name => available.find(a => a.Activity?.Name === name)))
|
||||
mbchc.run_activity(char, ag, action)
|
||||
} catch (error) {
|
||||
mbchc.report(error)
|
||||
|
@ -456,9 +438,9 @@ w.MBCHC = {
|
|||
},
|
||||
bell() {
|
||||
setTimeout(() => {
|
||||
style('#InputChat', s => s.outline = '')
|
||||
U.style('#InputChat', s => s.outline = '')
|
||||
}, 100)
|
||||
style('#InputChat', s => s.outline = 'solid red')
|
||||
U.style('#InputChat', s => s.outline = 'solid red')
|
||||
},
|
||||
complete(options, space = true) {
|
||||
if (options.length === 0) return (this.bell())
|
||||
|
@ -475,22 +457,22 @@ w.MBCHC = {
|
|||
|
||||
if (pref) this.complete([pref], false)
|
||||
this.comp_hint(options)
|
||||
} else w.ElementValue('InputChat', w.ElementValue('InputChat').replace(this.RE_LAST_WORD, `$1${options[0]}${space ? ' ' : ''}`))
|
||||
} else W.ElementValue('InputChat', W.ElementValue('InputChat').replace(this.RE_LAST_WORD, `$1${options[0]}${space ? ' ' : ''}`))
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays strings as completion hint
|
||||
* @param {string[]} options List of words to display. The order will be modified without copy.
|
||||
* @returns {void}
|
||||
* @returns {undefined}
|
||||
*/
|
||||
comp_hint(options) {
|
||||
if (options.length === 0) return
|
||||
this.COMP_HINT.innerHTML = '<div>' + options.sort().reverse().map(s => `<div>${s}</div>`).join('') + '</div>'
|
||||
this.COMP_HINT.style.display = 'block'
|
||||
w.ElementSetDataAttribute(this.COMP_HINT.id, 'colortheme', (w.Player.ChatSettings.ColorTheme || 'Light'))
|
||||
const rescroll = w.ElementIsScrolledToEnd('TextAreaChatLog')
|
||||
w.ChatRoomResize(false)
|
||||
if (rescroll) w.ElementScrollToEnd('TextAreaChatLog')
|
||||
W.ElementSetDataAttribute(this.COMP_HINT.id, 'colortheme', (W.Player.ChatSettings?.ColorTheme || 'Light'))
|
||||
const rescroll = W.ElementIsScrolledToEnd('TextAreaChatLog')
|
||||
W.ChatRoomResize(false)
|
||||
if (rescroll) W.ElementScrollToEnd('TextAreaChatLog')
|
||||
this.COMP_HINT.firstChild?.lastChild?.scrollIntoView({behaviour: 'instant'})
|
||||
},
|
||||
|
||||
|
@ -504,12 +486,12 @@ w.MBCHC = {
|
|||
comp_hint_hide() {
|
||||
if (!this.comp_hint_visible()) return
|
||||
this.COMP_HINT.style.display = 'none'
|
||||
w.ChatRoomResize(false)
|
||||
W.ChatRoomResize(false)
|
||||
},
|
||||
complete_target(token, me2 = true, check_perms = false) {
|
||||
const [locase, found] = [token.toLocaleLowerCase(), new Set()]
|
||||
for (const c of w.ChatRoomCharacter) {
|
||||
if ((c.IsPlayer() && !me2) || (check_perms && !w.ServerChatRoomGetAllowItem(w.Player, c))) continue
|
||||
for (const c of W.ChatRoomCharacter) {
|
||||
if ((c.IsPlayer() && !me2) || (check_perms && !W.ServerChatRoomGetAllowItem(W.Player, c))) continue
|
||||
for (const s of this.char2targets(c)) {
|
||||
if (s.toLocaleLowerCase().startsWith(locase)) found.add(s)
|
||||
}
|
||||
|
@ -519,12 +501,12 @@ w.MBCHC = {
|
|||
},
|
||||
complete_common() {
|
||||
// w.ElementValue('InputChat') will strip the trailing whitespace
|
||||
const E = document.querySelector('#InputChat')
|
||||
const E = D.querySelector('#InputChat')
|
||||
if (!(E && E instanceof HTMLTextAreaElement)) throw new Error('somehow InputChat is broken')
|
||||
return ([this, E.value, this.tokenise(E.value)])
|
||||
},
|
||||
complete_mbchc(_args, _locase, _cmdline) {
|
||||
const [mbchc, _input, tokens] = w.MBCHC.complete_common(); // `this` is command object
|
||||
const [mbchc, _input, tokens] = W.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()
|
||||
|
@ -539,11 +521,11 @@ w.MBCHC = {
|
|||
complete_do_target(actions, token) {
|
||||
if (!actions) return
|
||||
const me2 = (actions.self.length > 0)
|
||||
if (me2 && actions.others.length === 0) return (this.complete([cid(w.Player).toString()])) // Target is always the player
|
||||
if (me2 && actions.others.length === 0) return (this.complete([U.cid(W.Player).toString()])) // Target is always the player
|
||||
this.complete_target(token, me2, true)
|
||||
},
|
||||
complete_do(_args, _locase, _cmdline) {
|
||||
const [mbchc, _input, tokens] = w.MBCHC.complete_common(); // `this` is command object
|
||||
const [mbchc, _input, tokens] = W.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
|
||||
|
@ -566,22 +548,22 @@ w.MBCHC = {
|
|||
mbchc.bell()
|
||||
},
|
||||
complete_fbc_anim(_args, _locase, _cmdline) {
|
||||
const [mbchc, _input, tokens] = w.MBCHC.complete_common(); // `this` is command object
|
||||
const [mbchc, _input, tokens] = W.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(w.bce_EventExpressions).filter(a => a.toLocaleLowerCase().startsWith(anim))))
|
||||
return (mbchc.complete(Object.keys(W.bce_EventExpressions).filter(a => a.toLocaleLowerCase().startsWith(anim))))
|
||||
},
|
||||
complete_fbc_pose(_args, _locase, _cmdline) {
|
||||
const [mbchc, _input, tokens] = w.MBCHC.complete_common(); // `this` is command object
|
||||
const [mbchc, _input, tokens] = W.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(w.PoseFemale3DCG.map(p => p.Name).filter(p => p.toLocaleLowerCase().startsWith(pose))))
|
||||
return (mbchc.complete(W.PoseFemale3DCG.map(p => p.Name).filter(p => p.toLocaleLowerCase().startsWith(pose))))
|
||||
},
|
||||
history(down) {
|
||||
const [text, history] = [w.ElementValue('InputChat'), w.ChatRoomLastMessage]
|
||||
const [text, history] = [W.ElementValue('InputChat'), W.ChatRoomLastMessage]
|
||||
if (!this.HISTORY_MODE) {
|
||||
history.push(text)
|
||||
this.HISTORY_MODE = true
|
||||
|
@ -589,15 +571,15 @@ w.MBCHC = {
|
|||
|
||||
const ids = history.map((t, i) => ({t, i})).filter(r => r.t.startsWith(history.at(-1))).map(r => r.i)
|
||||
if (!down) ids.reverse()
|
||||
const found = ids.find(id => (down) ? id > w.ChatRoomLastMessageIndex : id < w.ChatRoomLastMessageIndex)
|
||||
const found = ids.find(id => (down) ? id > W.ChatRoomLastMessageIndex : id < W.ChatRoomLastMessageIndex)
|
||||
if (!found) return (this.bell())
|
||||
w.ElementValue('InputChat', history[found])
|
||||
w.ChatRoomLastMessageIndex = found
|
||||
W.ElementValue('InputChat', history[found])
|
||||
W.ChatRoomLastMessageIndex = found
|
||||
},
|
||||
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 !w.ChatRoomMapViewIsActive()
|
||||
if (D.activeElement === D.body) return true
|
||||
if (D.activeElement?.id !== 'MainCanvas') return false
|
||||
return !W.ChatRoomMapViewIsActive()
|
||||
},
|
||||
focus_chat_whitelist(event) {
|
||||
if (event.ctrlKey && event.key === 'v') return true // Ctrl+V should paste
|
||||
|
@ -607,8 +589,8 @@ w.MBCHC = {
|
|||
if (event.repeat) return // Only unique presses please
|
||||
if (!this.focus_chat_checks()) return
|
||||
if ([event.altKey, event.ctrlKey, event.metaKey].some(Boolean) && !this.focus_chat_whitelist(event)) return // Alt, ctrl and meta should all be false
|
||||
if (style('#InputChat', s => s.display) !== 'inline') return // Input chat missing
|
||||
w.ElementFocus('InputChat')
|
||||
if (U.style('#InputChat', s => s.display) !== 'inline') return // Input chat missing
|
||||
W.ElementFocus('InputChat')
|
||||
},
|
||||
loader() {
|
||||
if (this.remove_load_hook) {
|
||||
|
@ -617,18 +599,21 @@ w.MBCHC = {
|
|||
}
|
||||
|
||||
if (this.LOADED) return
|
||||
|
||||
U.with(/** @type {MBCHC.Settings.V0} */ (W.Player.OnlineSettings), os => os?.MBCHC && Settings.migrate_0_1(os))
|
||||
|
||||
// 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},
|
||||
]
|
||||
this.CommandsKey = CommandsKey /* eslint-disable-line no-undef */ // window.CommandsKey is undefined
|
||||
this.CommandsKey = CommandsKey
|
||||
this.RE_ACTIVITY = new RegExp(`^${this.CommandsKey}activity `)
|
||||
this.PREF_ACTIVITY = `${this.CommandsKey}activity `
|
||||
this.COMP_HINT = document.createElement('div')
|
||||
this.COMP_HINT = D.createElement('div')
|
||||
this.COMP_HINT.id = 'mbchcCompHint'
|
||||
const css = document.createElement('style')
|
||||
const css = D.createElement('style')
|
||||
css.textContent = `
|
||||
#TextAreaChatLog .mbchc, #TextAreaChatLog .mbchc {
|
||||
background-color: ${this.RGB_POLLY};
|
||||
|
@ -659,47 +644,48 @@ w.MBCHC = {
|
|||
}
|
||||
`
|
||||
|
||||
document.head.append(css)
|
||||
D.head.append(css)
|
||||
// Actions
|
||||
this.calculate_maps()
|
||||
//w.Player.MBCHC = {VERSION: this.VERSION}
|
||||
w.CommandCombine(COMMANDS)
|
||||
W.CommandCombine(COMMANDS)
|
||||
|
||||
// Hooks
|
||||
this.remove_fbc_hook = this.before('MainRun', () => w.bce_ActivityTriggers && this.patch_fbc())
|
||||
this.after('CharacterOnlineRefresh', char => this.update_char(char))
|
||||
this.remove_fbc_hook = this.before('MainRun', () => W.bce_ActivityTriggers && this.patch_fbc())
|
||||
// this.after('CharacterOnlineRefresh', char => this.update_char(char))
|
||||
this.after('ChatRoomReceiveSuitcaseMoney', () => {
|
||||
if (this.AUTOHACK_ENABLED && this.LAST_HACKED) {
|
||||
w.CurrentCharacter = this.cid2char(this.LAST_HACKED)
|
||||
W.CurrentCharacter = this.cid2char(this.LAST_HACKED)
|
||||
this.LAST_HACKED = null
|
||||
w.ChatRoomTryToTakeSuitcase()
|
||||
W.ChatRoomTryToTakeSuitcase()
|
||||
}
|
||||
})
|
||||
this.before('ChatRoomSendChat', () => {
|
||||
let input = w.ElementValue('InputChat')
|
||||
let input = W.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)
|
||||
w.ElementValue('InputChat', input)
|
||||
W.ElementValue('InputChat', input)
|
||||
}
|
||||
})
|
||||
this.after('ChatRoomSendChat', () => {
|
||||
const history = w.ChatRoomLastMessage
|
||||
const history = W.ChatRoomLastMessage
|
||||
if ((history.length > 1) && (history.at(-1) === history.at(-2))) {
|
||||
history.pop()
|
||||
w.ChatRoomLastMessageIndex -= 1
|
||||
W.ChatRoomLastMessageIndex -= 1
|
||||
}
|
||||
})
|
||||
this.before('ChatRoomCharacterViewDrawOverlay', (C, CharX, CharY, Zoom, _Pos) => {
|
||||
// if (w.ChatRoomHideIconState < 1 && C.MBCHC) {
|
||||
// w.DrawRect(CharX + (175 * Zoom), CharY, 50 * Zoom, 50 * Zoom, C.MBCHC.VERSION === w.Player.MBCHC.VERSION ? this.RGB_POLLY : this.RGB_MUTE)
|
||||
// }
|
||||
if (w.ChatRoomHideIconState < 1 && C.MBCHC_LOCAL && typeof C.MBCHC_LOCAL.TZ === 'number') {
|
||||
const hours = new Date(w.CommonTime() + this.UTC_OFFSET + (C.MBCHC_LOCAL.TZ * 60 * 60 * 1000)).getHours()
|
||||
w.DrawTextFit(hours < 10 ? '0' + hours.toString() : hours.toString(), CharX + (200 * Zoom), CharY + (25 * Zoom), 46 * Zoom, 'white', 'black')
|
||||
const ctz = TZ.for(C)
|
||||
if (W.ChatRoomHideIconState < 1 && ctz !== undefined) {
|
||||
const hours = new Date(W.CommonTime() + this.UTC_OFFSET + (ctz * 60 * 60 * 1000)).getHours()
|
||||
W.DrawTextFit(hours < 10 ? '0' + hours.toString() : hours.toString(), CharX + (200 * Zoom), CharY + (25 * Zoom), 46 * Zoom, 'white', 'black')
|
||||
}
|
||||
})
|
||||
this.after('ChatRoomCreateElement', () => this.COMP_HINT.parentElement || document.body.append(this.COMP_HINT))
|
||||
this.after('ChatRoomCreateElement', () => this.COMP_HINT.parentElement || D.body.append(this.COMP_HINT))
|
||||
this.before('ChatRoomClearAllElements', () => {
|
||||
this.comp_hint_hide()
|
||||
this.COMP_HINT.remove()
|
||||
|
@ -708,33 +694,33 @@ w.MBCHC = {
|
|||
this.comp_hint_hide()
|
||||
})
|
||||
this.after('ChatRoomResize', () => {
|
||||
if (w.CharacterGetCurrent() === null && w.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
|
||||
if (W.CharacterGetCurrent() === null && W.CurrentScreen === 'ChatRoom' && D.querySelector('#InputChat') && D.querySelector('#TextAreaChatLog') && this.comp_hint_visible()) { // Upstream
|
||||
const fontsize = ChatRoomFontSize
|
||||
//w.ElementPositionFix('TextAreaChatLog', fontsize, 1005, 66, 988, 630)
|
||||
//w.ElementPositionFix(this.COMP_HINT.id, fontsize, 1005, 701, 988, 200)
|
||||
w.ElementPositionFix(this.COMP_HINT.id, fontsize, 800, 65, 200, 835)
|
||||
W.ElementPositionFix(this.COMP_HINT.id, fontsize, 800, 65, 200, 835)
|
||||
//this.COMP_HINT.style.display = 'flex'
|
||||
}
|
||||
})
|
||||
document.addEventListener('keydown', event => this.focus_chat(event))
|
||||
D.addEventListener('keydown', event => this.focus_chat(event))
|
||||
this.SDK.hookFunction('ChatRoomKeyDown', 0, (nextargs, next) => { // This fires on chat input events
|
||||
const [event] = nextargs
|
||||
w.MBCHC.comp_hint_hide()
|
||||
if ((w.KeyPress === 33) || (w.KeyPress === 34)) { // Better history
|
||||
W.MBCHC.comp_hint_hide()
|
||||
if ((W.KeyPress === 33) || (W.KeyPress === 34)) { // Better history
|
||||
event.preventDefault()
|
||||
return (w.MBCHC.history(w.KeyPress - 33))
|
||||
return (W.MBCHC.history(W.KeyPress - 33))
|
||||
}
|
||||
|
||||
if (w.MBCHC.HISTORY_MODE) {
|
||||
w.ChatRoomLastMessage.pop()
|
||||
w.MBCHC.HISTORY_MODE = false
|
||||
if (W.MBCHC.HISTORY_MODE) {
|
||||
W.ChatRoomLastMessage.pop()
|
||||
W.MBCHC.HISTORY_MODE = false
|
||||
}
|
||||
|
||||
return (next(nextargs))
|
||||
})
|
||||
|
||||
// Chat room handlers
|
||||
w.ChatRoomRegisterMessageHandler({Priority: -220, Description: 'MBCHC preprocessor', Callback: (data, sender, message, metadata) => {
|
||||
W.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})
|
||||
|
@ -742,7 +728,7 @@ w.MBCHC = {
|
|||
}})
|
||||
//w.ChatRoomRegisterMessageHandler({Priority: -219, Description: 'MBCHC room enter hook',
|
||||
// Callback: (data, _sender, _message, _metadata) => {
|
||||
// if ((data.Type === 'Action') && (data.Content === 'ServerEnter') && (data.Sender === cid(w.Player))) this.player_enters_room()
|
||||
// if ((data.Type === 'Action') && (data.Content === 'ServerEnter') && (data.Sender === U.cid(w.Player))) this.player_enters_room()
|
||||
// return false
|
||||
// },
|
||||
//})
|
||||
|
@ -752,7 +738,7 @@ w.MBCHC = {
|
|||
// return false
|
||||
// },
|
||||
//})
|
||||
w.ChatRoomRegisterMessageHandler({Priority: -219, Description: 'MBCHC autohack lookup',
|
||||
W.ChatRoomRegisterMessageHandler({Priority: -219, Description: 'MBCHC autohack lookup',
|
||||
Callback: (data, _sender, _message, _metadata) => {
|
||||
if ((data.Type === 'Hidden') && (data.Content === 'ReceiveSuitcaseMoney')) this.LAST_HACKED = data.Sender
|
||||
return false
|
||||
|
@ -762,15 +748,15 @@ w.MBCHC = {
|
|||
// Footer
|
||||
this.LOADED = true
|
||||
this.log('info', `loaded version ${this.VERSION}`)
|
||||
if ((w.CurrentModule === 'Online') && (w.CurrentScreen === 'ChatRoom')) {
|
||||
for (const c of w.ChatRoomCharacter) this.update_char(c)
|
||||
//this.player_enters_room()
|
||||
}
|
||||
//if ((W.CurrentModule === 'Online') && (W.CurrentScreen === 'ChatRoom')) {
|
||||
// for (const c of W.ChatRoomCharacter) this.update_char(c)
|
||||
// //this.player_enters_room()
|
||||
//}
|
||||
},
|
||||
preloader() {
|
||||
if (!w.AsylumGGTSSAddItems) throw new Error('AsylumGGTSSAddItems() not found, aborting MBCHC loading')
|
||||
if (!w.bcModSdk) throw new Error('SDK not found, please load with (or after) FUSAM')
|
||||
this.SDK = w.bcModSdk.registerMod({name: 'MBCHC', fullName: 'Mute\'s Bondage Club Hacks Collection', version: this.VERSION, repository: 'https://code.fleshless.org/mute/MBCHC/'})
|
||||
if (!W.AsylumGGTSSAddItems) throw new Error('AsylumGGTSSAddItems() not found, aborting MBCHC loading')
|
||||
if (!W.bcModSdk) throw new Error('SDK not found, please load with (or after) FUSAM')
|
||||
this.SDK = W.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)
|
||||
|
@ -788,13 +774,9 @@ w.MBCHC = {
|
|||
}
|
||||
return result
|
||||
})
|
||||
// for some reason many (not all) hooks don't work if the mod is loaded in the room
|
||||
// to be honest I have no idea what's going on. the hooks get registered, they just don't get called by SDK.
|
||||
if (current() === 'Online/ChatRoom') throw new Error('please do not load in a chat room')
|
||||
if (current() === 'Character/Login') {
|
||||
this.remove_load_hook = this.before('AsylumGGTSSAddItems', () => this.loader())
|
||||
} else this.loader()
|
||||
|
||||
U.current() === 'Character/Login' ? this.remove_load_hook = this.before('AsylumGGTSSAddItems', () => this.loader()) :this.loader()
|
||||
},
|
||||
}
|
||||
|
||||
w.MBCHC.preloader()
|
||||
W.MBCHC.preloader()
|
||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "mbchc",
|
||||
"version": "0.0.12",
|
||||
"version": "105.13.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mbchc",
|
||||
"version": "0.0.12",
|
||||
"version": "105.13.0",
|
||||
"license": "SEE LICENSE IN LICENSE.",
|
||||
"devDependencies": {
|
||||
"bc-stubs": "^105.0.0",
|
||||
|
|
51
package.json
51
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mbchc",
|
||||
"version": "0.0.12",
|
||||
"version": "105.13.0",
|
||||
"description": "Mute's Bondage Club Hacks Collection",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
|
@ -9,12 +9,37 @@
|
|||
"bondage-club-mod-sdk": "^1.2.0"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE.",
|
||||
"xo": {
|
||||
"env": [
|
||||
"browser",
|
||||
"node"
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"xo",
|
||||
"xo-typescript"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": { "project": ["./jsconfig.json"] },
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"globals": {"GM": true, "GM_info": true},
|
||||
"rules": {
|
||||
"@typescript-eslint/brace-style": "off",
|
||||
"@typescript-eslint/comma-dangle": ["error", "only-multiline"],
|
||||
"@typescript-eslint/consistent-type-definitions": "off",
|
||||
"@typescript-eslint/consistent-type-imports": "off",
|
||||
"@typescript-eslint/member-delimiter-style": "off",
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"@typescript-eslint/no-confusing-void-expression": ["error", {
|
||||
"ignoreVoidOperator": true
|
||||
}],
|
||||
"@typescript-eslint/no-meaningless-void-operator": "off",
|
||||
"@typescript-eslint/object-curly-spacing": "off",
|
||||
"@typescript-eslint/padding-line-between-statements": "off",
|
||||
"@typescript-eslint/semi": "off",
|
||||
"@typescript-eslint/space-before-function-paren": "off",
|
||||
"@typescript-eslint/strict-boolean-expressions": ["error", {
|
||||
"allowString": false,
|
||||
"allowNumber": false,
|
||||
"allowNullableObject": false
|
||||
}],
|
||||
"array-element-newline": "off",
|
||||
"brace-style": "off",
|
||||
"camelcase": "off",
|
||||
"capitalized-comments": "off",
|
||||
|
@ -28,11 +53,27 @@
|
|||
"argsIgnorePattern": "^_",
|
||||
"destructuredArrayIgnorePattern": "^_"
|
||||
}],
|
||||
"no-void": "off",
|
||||
"padding-line-between-statements": "off",
|
||||
"object-curly-newline": "off",
|
||||
"one-var": "off",
|
||||
"one-var-declaration-per-line": "off",
|
||||
"semi": "off",
|
||||
"space-before-function-paren": "off",
|
||||
"spaced-comment": "off",
|
||||
"unicorn/no-array-for-each": "off",
|
||||
"unicorn/no-array-reduce": "off",
|
||||
"unicorn/prefer-module": "off",
|
||||
"unicorn/prefer-top-level-await": "off",
|
||||
"fake/fuck-commas": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.d.ts"],
|
||||
"rules": {
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
10
typedef.d.ts
vendored
10
typedef.d.ts
vendored
|
@ -1,10 +0,0 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/consistent-type-definitions */
|
||||
|
||||
interface PlayerOnlineSettings {
|
||||
MBCHC?: any;
|
||||
}
|
||||
|
||||
interface ServerChatRoomMessage {
|
||||
MBCHC_ID?: number;
|
||||
}
|
Loading…
Reference in New Issue
Block a user