您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The Soul 的搜索增强版,帮助快速定位目标牌和位置。内置 蓝图、头脑、可乐的搜索,其他自行调用函数找到 `window.findCardInAnte(底注, 英文牌名)`
// ==UserScript== // @name 小丑牌 Balatro 卡牌定位工具 // @namespace balatro-soul-util // @version 0.0.6 // @description The Soul 的搜索增强版,帮助快速定位目标牌和位置。内置 蓝图、头脑、可乐的搜索,其他自行调用函数找到 `window.findCardInAnte(底注, 英文牌名)` // @author liam61 // @match mathisfun0.github.io/The-Soul/ // @license MIT // @grant unsafeWindow // @grant GM_getValue // @grant GM.getValue // @grant GM_setValue // @grant GM.setValue // @charset UTF-8 // @github https://github.com/liam61/the-soul-card-util // ==/UserScript== ;(function (global) { const minAnte = 1 const maxAnte = 40 const presetCardsMap = { FlushFive: ['Blueprint', 'Brainstorm', 'Diet Cola', 'Showman', 'The Idol', 'Sock and Buskin'], 同花五条流: ['Blueprint', 'Brainstorm', 'Diet Cola', 'Showman', 'The Idol', 'Sock and Buskin'], HighCard: ['Blueprint', 'Brainstorm', 'Diet Cola', 'Showman', 'Baron', 'Mime'], 高牌流: ['Blueprint', 'Brainstorm', 'Diet Cola', 'Showman', 'Baron', 'Mime'], // Juggler, Troubadour LiamsFlushFive: [ 'Blueprint', 'Brainstorm', 'Diet Cola', { card: 'Scary Face', range: [1, 10] }, { card: 'Hanging Chad', range: [1, 18] }, { card: 'Photograph', range: [1, 18] }, { card: 'Midas Mask', range: [6, 9] }, { card: 'Golden Ticket', range: [8, 33] }, { card: 'Seance', range: [8, 10] }, { card: 'Showman', range: [10, 33] }, { card: 'Sock and Buskin', range: [16, 33] }, { card: 'The Idol', range: [18, 33] }, ], } /** * * @param {number} ante 底注 * @param {string | string[]} targets 目标牌,可以是单张牌,也可以是多张牌数组。可选参数 * @param {object} config 可选参数 * @param {'index' | 'card'} config.orderBy 排序方式,index: 按照位置顺序,card: 按照牌的顺序 * @param {'FlushFive' | 'HighCard'} config.preset 预设的牌组,FlushFive: 同花五条流,HighCard: 高牌流 * @param {2 | 3 | 4} config.stockSize 牌库大小,默认 3 */ const findCardsInAnte = (ante, targets = [], config) => { if (typeof ante !== 'number') { console.error('[ante error]: should be a number') alert('[ante error]: 底注应该是一个数字') } // 兼容未传 targets,TODO: 考虑其他优雅传参 if (targets && typeof targets === 'object' && !Array.isArray(targets) && config == null) { config = targets targets = [] } if (!(Array.isArray(targets) || typeof targets === 'string' || targets == null)) { console.error('[targets error]: should be an array or string') alert('[targets error]: 目标牌应该是一个数组或字符串') } if (!(typeof config === 'object' || config == null)) { console.error('[config error]: should be an object') alert('[config error]: 配置选项应该是一个对象') } const { orderBy = 'index', preset = 'FlushFive', stockSize = 3 } = config || {} // ==Utils== const parsePresetCard = (item) => { if (typeof item === 'object' && Array.isArray(item?.range)) { return { card: item.card, min: ~~+item.range[0] || minAnte, max: ~~+item.range[1] || maxAnte, } } if (typeof item === 'string') { return { card: item, min: minAnte, max: maxAnte, } } return {} } /** * * @param {number} ante * @param {( string | { card: string, range: [number, number] } )} target */ const findIndexOfCard = (ante, target) => { const { card, min, max } = parsePresetCard(target) if (typeof card !== 'string' || !(min <= ante && ante <= max)) return [] const anteEl = document.querySelector('#scrollingContainer').children[ante - 1] if (!anteEl) return [] const cardListEl = anteEl.querySelector('.scrollable') const arr = [...cardListEl.children].reduce((arr, cardEl, index) => { const el = cardEl.querySelector('div') if (el.textContent.includes(card)) { arr.push({ card, index }) } return arr }, []) return arr } const print = (targetCardList) => { const msgArr = targetCardList.map(({ card, index }) => { const realIndex = index + 1 const order = realIndex % stockSize return `[ante ${ante}] ${card.padEnd(20, ' ')}\tindex: ${realIndex}, round: ${Math.ceil( realIndex / stockSize )}, order: ${order != 0 ? order : stockSize}` }) if (targetCardList.length) { console.log(msgArr.join('\n')) console.log('\n') } } // ==/Utils== // ==Output== const finalTargets = targets?.length ? Array.isArray(targets) ? targets : [targets] : presetCardsMap[preset] // TODO: optimize algorithm const targetLists = finalTargets.map((target) => findIndexOfCard(ante, target)) let results = [] if (orderBy === 'card') { results = targetLists.sort((list1, list2) => list1[0]?.index - list2[0]?.index) results.forEach(print) } else if (orderBy === 'index') { results = targetLists .reduce((arr, item) => { arr.push(...item) return arr }, []) .sort((item1, item2) => item1.index - item2.index) print(results) } return results } // ==/Output== // ==Export== global.findCardsInAnte = findCardsInAnte global.findCardsInAllAntes = (targets, config) => { return Array.from({ length: maxAnte }, (_, i) => i + 1).map((ante) => findCardsInAnte(ante, targets, config) ) } // ==/Export== global.findCardsInAllAntes({ preset: 'LiamsFlushFive' }) })(typeof unsafeWindow !== 'undefined' ? unsafeWindow : window)