From 9ceb6ea2cd881a4e08fb168d90e27bc4ce2eed0d Mon Sep 17 00:00:00 2001 From: Mute Date: Sun, 13 Apr 2025 14:18:54 +0000 Subject: [PATCH] 0.0.3: added Kitty, plus a bit of cleanup --- .gitignore | 1 + ambient.d.ts | 3 ++- lily.user.js | 50 ++++++++++++++++++++++++++--------------------- package-lock.json | 12 ++++++------ package.json | 4 ++-- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 3c3629e..e982274 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +ideas.txt diff --git a/ambient.d.ts b/ambient.d.ts index 8836189..4d24f03 100644 --- a/ambient.d.ts +++ b/ambient.d.ts @@ -3,6 +3,7 @@ declare namespace LILY { remove_hook: undefined | (() => void) true(callback: () => unknown): true // stops eslint from complaining about constant bool expressions with(value: V, callback: (v: V) => R): R // A silly helper to kinda curry values + ass(value: V | undefined | null, callback: (v: V) => R): R | undefined /* ditto, but check for empty */ //eslint-disable-line @typescript-eslint/ban-types send(callback: () => boolean): boolean // updates the player if the function succeeds } @@ -36,7 +37,7 @@ declare namespace LILY { interface Commands extends Command {} interface Belt { admins: number[] - users: number[] + users: Record cli: Commands is_cb(subject: Commands | CommandCB): subject is CommandCB run(tokens: string[]): string diff --git a/lily.user.js b/lily.user.js index 9e5c90d..8203b71 100644 --- a/lily.user.js +++ b/lily.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name LILY // @namespace https://code.fleshless.org/mute -// @version 0.0.2 +// @version 0.0.3 // @description Lily's Integrated Logic Yoke // @grant none // @author Mute @@ -13,26 +13,28 @@ // ==/UserScript== (function() { 'use strict'; + const $ = undefined const W = /** @type {Window & typeof globalThis & {bcModSdk: import('bondage-club-mod-sdk').ModSDKGlobalAPI}} */ (window) const SDK = W.bcModSdk.registerMod({name: GM_info.script.name, fullName: GM_info.script.description ?? '', version: GM_info.script.version, repository: 'finger://your.mom'}) - /** @type {LILY.Utils} */ const U = {remove_hook: undefined, + /** @type {LILY.Utils} */ const U = {remove_hook: $, true(f) {f(); return true}, with: (v, f) => f(v), + ass: (v, f) => v === $ || v === null ? $ : U.with(f(v), r => r === null ? $ : r), //eslint-disable-line @typescript-eslint/prefer-nullish-coalescing send: f => f() && U.true(() => void W.ChatRoomCharacterUpdate(W.Player)), } /** @type {LILY.Box} */ const Box = { - enable(item) { if (item === undefined) return false // TODO Okay, we need to be more clever here. The light should be above the box itself + enable(item) { if (item === $) return false // TODO Okay, we need to be more clever here. The light should be above the box itself return U.with(item.Property, ip => typeof ip?.OverridePriority === 'object' && (ip.OverridePriority['Light'] = 41) > 0) }, - disable(item) { return item === undefined ? false : U.with(item.Property ??= {}, ip => U.with(ip.OverridePriority ??= {}, op => + disable(item) { return item === $ ? false : U.with(item.Property ??= {}, ip => U.with(ip.OverridePriority ??= {}, op => typeof op === 'number' ? Boolean(ip.OverridePriority = {Lock: op, Unit: op}) : ((op['Light'] = 0) < 1) ))}, on: () => U.with(Box.item, i => U.send(() => Box.enable(i))), off: () => U.with(Box.item, i => U.send(() => Box.disable(i))), - get item() {return U.with(W.InventoryGet(W.Player, 'ItemNeckAccessories'), item => item?.Craft?.Name === 'Lily\'s voicebox' ? item : undefined)}, - get enabled() {return U.with(Box.item, i => i !== undefined && U.with(i?.Property?.OverridePriority, op => typeof op !== 'object' || !Object.hasOwn(op, 'Light') || (op['Light'] ?? 1) > 0))}, // TODO check if the light is above the box + get item() {return U.ass(W.Player, p => U.ass(Belt.users[p.MemberNumber ?? -1], u => U.ass(W.InventoryGet(p, u.zone), i => i.Craft?.Name === u.name ? i : $)))}, + get enabled() {return U.with(Box.item, i => i !== $ && U.with(i?.Property?.OverridePriority, op => typeof op !== 'object' || !Object.hasOwn(op, 'Light') || (op['Light'] ?? 1) > 0))}, // TODO check if the light is above the box } - /** @type {LILY.Eyes} */ const Eyes = {timeout: undefined, zones: ['ItemHead', 'Mask'], + /** @type {LILY.Eyes} */ const Eyes = {timeout: $, zones: ['ItemHead', 'Mask'], rgb: [ '#990000', '#990f00', '#991f00', '#992e00', '#993d00', '#994d00', '#995c00', '#996b00', '#997a00', '#998a00', '#999900', '#8a9900', '#7a9900', '#6b9900', '#5c9900', '#4d9900', '#3d9900', '#2e9900', '#1f9900', '#0f9900', @@ -43,16 +45,21 @@ ], craft: {Item: 'AnimeLenses', Name: 'Akihabara souvenir', Description: 'you will never be the same uwu', Color: '#FFFFFF,Default,#FFFFFF,Default', Property: 'Thick', Lock: '', Private: true, ItemProperty: {}, Type: null, TypeRecord: null, MemberNumber: 71_240, MemberName: 'Mute'}, set_rgb: (item, n) => U.true(() => item.Color = [Eyes.rgb[n] ?? 'Default', 'Default', Eyes.rgb[n] ?? 'Default', 'Default']), - next_rgb: () => Eyes.check('ItemHead') === undefined && U.with(Eyes.check('Mask'), i => i !== undefined && U.with(Eyes.rgb.indexOf(i.Color?.[0] ?? Eyes.rgb[0] ?? '') + 1, n => Eyes.set_rgb(i, n))), + next_rgb: () => Eyes.check('ItemHead') === $ && U.with(Eyes.check('Mask'), i => i !== $ && U.with(Eyes.rgb.indexOf(i.Color?.[0] ?? Eyes.rgb[0] ?? '') + 1, n => Eyes.set_rgb(i, n))), roll_rgb: () => Eyes.next_rgb() && void W.ChatRoomCharacterItemUpdate(W.Player, 'Mask'), - start: () => Eyes.check('Mask') !== undefined && Eyes.timeout === undefined && Boolean(Eyes.timeout = setInterval(Eyes.roll_rgb, 1000)), - stop: () => Eyes.check('Mask') !== undefined && Eyes.timeout !== undefined && U.true(() => Eyes.timeout = void clearInterval(Eyes.timeout)), - dim: () => U.send(() => Eyes.check('ItemHead') === undefined && U.true(() => W.InventoryWear(W.Player, 'AnimeLenses', 'ItemHead', ['#FFFFFF', 'Default', '#FFFFFF', 'Default'], undefined, 71_240, Eyes.craft))), - clear: () => U.send(() => Eyes.check('ItemHead') !== undefined && U.true(() => void W.InventoryRemove(W.Player, 'ItemHead'))), - check: zone => U.with(Eyes.item, item => item?.Asset.Group.Name === zone ? item : undefined), - get item() {return Eyes.zones.map(z => W.InventoryGet(W.Player, z)).find(item => item?.Asset.Name === 'AnimeLenses') ?? undefined}, + start: () => Eyes.check('Mask') !== $ && Eyes.timeout === $ && Boolean(Eyes.timeout = setInterval(Eyes.roll_rgb, 1000)), + stop: () => Eyes.check('Mask') !== $ && Eyes.timeout !== $ && U.true(() => Eyes.timeout = void clearInterval(Eyes.timeout)), + dim: () => U.send(() => Eyes.check('ItemHead') === $ && U.true(() => W.InventoryWear(W.Player, 'AnimeLenses', 'ItemHead', ['#FFFFFF', 'Default', '#FFFFFF', 'Default'], $, 71_240, Eyes.craft))), + clear: () => U.send(() => Eyes.check('ItemHead') !== $ && U.true(() => void W.InventoryRemove(W.Player, 'ItemHead'))), + check: zone => U.with(Eyes.item, item => item?.Asset.Group.Name === zone ? item : $), + get item() {return Eyes.zones.map(z => W.InventoryGet(W.Player, z)).find(item => item?.Asset.Name === 'AnimeLenses') ?? $}, } - /** @type {LILY.Belt} */ const Belt = {users: [119_643, 154_662, 62_808], admins: [71_240, 67_994], + /** @type {LILY.Belt} */ const Belt = {admins: [71_240, 67_994], + users: { + 119_643: {zone: 'ItemNeckAccessories', name: 'Lily\'s voicebox'}, + //154_662: '?hell if I remember?', + 62_808: {zone: 'ItemNeck', name: 'CKO voicebox'}, + }, cli: {'#LILY': { status: _ => '200 OK', box: {on: Box.on, off: Box.off}, @@ -60,21 +67,20 @@ }}, /** @type {LILY.Belt['is_cb']} */ is_cb: s => typeof s === 'function', run(tokens) { /** @type {string | undefined} */ let t, /** @type {LILY.Commands | LILY.CommandCB} */ cmd = Belt.cli - while ((t = tokens.shift()) !== undefined) if (U.with(cmd[t], next => next !== undefined && Belt.is_cb(cmd = next))) break // eslint-disable-line @typescript-eslint/no-loop-func - if (!Belt.is_cb(cmd)) return t === undefined ? `subcommands: [${Object.keys(cmd).join(', ')}]` : `unknown token ${t}` + while ((t = tokens.shift()) !== $) if (U.with(cmd[t], next => next !== $ && Belt.is_cb(cmd = next))) break // eslint-disable-line @typescript-eslint/no-loop-func + if (!Belt.is_cb(cmd)) return t === $ ? `subcommands: [${Object.keys(cmd).join(', ')}]` : `unknown token ${t}` return U.with(cmd(...tokens), r => typeof r === 'boolean' ? (r ? 'success' : 'fail') : String(r)) }, - receive(data) { if (data.Sender === undefined) return undefined + receive(data) { if (data.Sender === $) return $ W.ChatRoomSendLocal(`${data.Content}`) return void W.ChatRoomSendWhisper(data.Sender, `LILY: ${Belt.admins.includes(data.Sender) ? Belt.run(data.Content.split(' ')) : 'access denied'}`) }, } /** @type {(text: string) => undefined} */ const unload = text => U.true(SDK.unload) && void console.debug(`LILY: ${text}, unloading the mod`) const load_after_login = () => { - if (U.remove_hook !== undefined) {U.remove_hook?.(); U.remove_hook = undefined} - if (W.Player.MemberNumber === undefined) return void unload('member not identified') - if (!Belt.users.includes(W.Player.MemberNumber)) return void unload('member not eligible for loading') - // SDK.hookFunction('SpeechTransformGagGarble', 0, ([text]) => text) // disable garbling // unfortunately affects hearing as well + if (U.remove_hook !== $) {U.remove_hook?.(); U.remove_hook = $} + if (W.Player.MemberNumber === $) return void unload('member not identified') + if (!Object.hasOwn(Belt.users, W.Player.MemberNumber)) return void unload('member not eligible for loading') SDK.hookFunction('SpeechTransformGagGarbleIntensity', 0, _ => 0) // disable garbling SDK.hookFunction('ChatRoomSendChatMessage', 0, (na, n) => Box.enabled || na[0].startsWith('(') ? n(na) : !U.true(() => void W.ChatRoomSendLocal('You try to talk, but nothing comes out.'))) W.ChatRoomRegisterMessageHandler({Priority: -169, Description: 'LILY', Callback: data => data.Type === 'Whisper' && data.Content.startsWith('#LILY') && U.true(() => void Belt.receive(data))}) diff --git a/package-lock.json b/package-lock.json index 8184a73..1db08bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "lily", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lily", - "version": "0.0.2", + "version": "0.0.3", "license": "SEE LICENSE IN LICENSE.", "devDependencies": { "@types/tampermonkey": "^5.0.3", - "bc-stubs": "^108.0.0", + "bc-stubs": "^114.0.0", "bondage-club-mod-sdk": "^1.2.0", "xo": "^0.58.0" } @@ -1098,9 +1098,9 @@ "dev": true }, "node_modules/bc-stubs": { - "version": "108.0.0", - "resolved": "https://registry.npmjs.org/bc-stubs/-/bc-stubs-108.0.0.tgz", - "integrity": "sha512-b3ko8zl9zn5umYKC7uMQjEtOQxcbAdalUvJ7DoOPyJX4WcsdJFq+R76Rr8bhTtcRq7aW3tk7DtDjTy9awd7CRw==", + "version": "114.0.0", + "resolved": "https://registry.npmjs.org/bc-stubs/-/bc-stubs-114.0.0.tgz", + "integrity": "sha512-o5G1ryUDwOez2GdnpLChGYVRiR8981PHOZNUnWuquSbQf5eNTqdq3sWtEF9vgpyJjlodNPBuoNQj+KlOeWOAjQ==", "dev": true, "dependencies": { "socket.io-client": "4.6.1" diff --git a/package.json b/package.json index d8226be..f6164c7 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "lily", - "version": "0.0.2", + "version": "0.0.3", "description": "Lily's Integrated Logic Yoke", "type": "module", "private": true, "devDependencies": { "xo": "^0.58.0", - "bc-stubs": "^108.0.0", + "bc-stubs": "^114.0.0", "bondage-club-mod-sdk": "^1.2.0", "@types/tampermonkey": "^5.0.3" },