您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
对三采和烹饪冲泡,添加一个数量栏显示期望产物数量,也可输入期望数量反推期望采集次数。
当前为
// ==UserScript== // @name MWI三采吃喝期望数量助手 // @namespace http://tampermonkey.net/ // @version 2.4.2 // @description 对三采和烹饪冲泡,添加一个数量栏显示期望产物数量,也可输入期望数量反推期望采集次数。 // @author zqzhang1996 // @match https://www.milkywayidle.com/* // @match https://test.milkywayidle.com/* // @grant none // @run-at document-body // @license MIT // ==/UserScript== (function () { 'use strict'; // 判断操作类型 function getCurrentSkillType() { const valDiv = document.querySelector('[class^="SkillActionDetail_value"]'); if (!valDiv) return null; const use = valDiv.querySelector('svg use'); if (!use) return null; const href = use.getAttribute('href') || ''; const match = href.match(/#([a-zA-Z0-9_]+)$/); return match ? match[1] : null; } // 判断操作类型 function getCurrentActionName() { const valDiv = document.querySelector('[class^="SkillActionDetail_name"]'); if (!valDiv) return null; return valDiv.textContent.trim(); } // 判断是否有采集茶(有才加15%),通过ItemSelector_itemContainer下查找 function hasGatheringTea() { const teaContainers = document.querySelectorAll('[class^="ItemSelector_itemContainer"]'); for (const container of teaContainers) { const teaSvg = container.querySelector('svg[aria-label="采集茶"]'); if (teaSvg) { const countDiv = container.querySelector('[class^="Item_count"]'); const count = countDiv ? parseInt(countDiv.textContent.replace(/,/g, ''), 10) : 0; if (count > 0) return true; } } return false; } // 判断是否有美食茶 function hasGourmetTea() { const teaContainers = document.querySelectorAll('[class^="ItemSelector_itemContainer"]'); for (const container of teaContainers) { const teaSvg = container.querySelector('svg[aria-label="美食茶"]'); if (teaSvg) { const countDiv = container.querySelector('[class^="Item_count"]'); const count = countDiv ? parseInt(countDiv.textContent.replace(/,/g, ''), 10) : 0; if (count > 0) return true; } } return false; } function getCommunityGatheringBuffLevel() { const buffDivs = document.querySelectorAll('[class^="CommunityBuff_communityBuff"]'); for (const buffDiv of buffDivs) { const useEl = buffDiv.querySelector('svg use'); if (!useEl) continue; const href = useEl.getAttribute('href') || ''; if (href.includes('gathering')) { const levelDiv = buffDiv.querySelector('[class^="CommunityBuff_level"]'); if (levelDiv) { const match = levelDiv.textContent.match(/Lv\.(\d+)/); if (match) return parseInt(match[1], 10); } } } return null; } function getBuffPercent() { // 判断操作类型 const skillType = getCurrentSkillType(); if (skillType === 'milking' || skillType === 'foraging' || skillType === 'woodcutting') { let total = 0; if (hasGatheringTea()) total += 0.15; const communityLevel = getCommunityGatheringBuffLevel(); if (communityLevel) { total += 0.20 + (communityLevel - 1) * 0.005; } return total; } else if (skillType === 'cooking' || skillType === 'brewing') { if (hasGourmetTea()) { return 0.12; } else { return 0; } } else { return null; // 其他类型不处理 } } // 获取采集区间(原始整数),返回 {rawMin, rawMax, minShow, maxShow, buff} function getGatherRangeWithRaw() { const skillType = getCurrentSkillType(); if (skillType === 'cooking' || skillType === 'brewing') { const buff = getBuffPercent(); if (buff === null) return null; return {rawMin: 1, rawMax: 1, minShow: 1 * (1 + buff), maxShow: 1 * (1 + buff), buff}; } if (skillType === 'milking' || skillType === 'foraging' || skillType === 'woodcutting') { const dropTable = document.querySelector('[class^="SkillActionDetail_dropTable"]'); if (!dropTable) return null; const drop = dropTable.querySelector('[class^="SkillActionDetail_drop"]'); if (!drop) return null; const numDiv = drop.querySelector(':scope > div:first-child'); if (!numDiv) return null; const txt = numDiv.textContent.trim(); let minShow = 0, maxShow = 0; if (txt.includes('-')) { let [a, b] = txt.split('-').map(s => parseFloat(s)); minShow = a; maxShow = b; } else { minShow = maxShow = parseFloat(txt); } const buff = getBuffPercent(); if (buff === null) return null; const rawMin = Math.round(minShow / (1 + buff)); const rawMax = Math.round(maxShow / (1 + buff)); return {rawMin, rawMax, minShow, maxShow, buff}; } // 其他类型不处理 return null; } function getTotalRangeFromTimes(times) { const range = getGatherRangeWithRaw(); if (!range) return {minTotal: '', maxTotal: '', expected: ''}; const {rawMin, rawMax, buff} = range; const minTotal = Math.floor(times * rawMin * (1 + buff)); const maxTotal = Math.floor(times * rawMax * (1 + buff)); const expected = ((rawMin + rawMax) / 2) * times * (1 + buff); return {minTotal, maxTotal, expected}; } function getTimesFromQty(qty) { const range = getGatherRangeWithRaw(); if (!range) return 1; const {rawMin, rawMax, buff} = range; if (rawMax <= 0) return 1; const perExpected = ((rawMin + rawMax) / 2) * (1 + buff); return Math.ceil(qty / perExpected - 1e-6); } // React input hack function reactInputTriggerHack(inputElem, value) { let lastValue = inputElem.value; inputElem.value = value; let event = new Event("input", { bubbles: true }); event.simulated = true; let tracker = inputElem._valueTracker; if (tracker) { tracker.setValue(lastValue); } inputElem.dispatchEvent(event); } // 复制原始结构并返回“数量”栏及input function createQuantityInputBlock() { const origBlock = document.querySelector('[class^="SkillActionDetail_maxActionCountInput"]'); if (!origBlock) return null; // 只克隆外层div(不带子内容) const newBlock = origBlock.cloneNode(false); // label const origLabel = origBlock.querySelector('[class^="SkillActionDetail_label"]'); const label = origLabel.cloneNode(true); label.textContent = '数量'; // 输入部分 const origInputWrap = origBlock.querySelector('[class^="SkillActionDetail_input"]'); const inputWrap = origInputWrap.cloneNode(false); // input container const origInputContainer = origInputWrap.querySelector('[class^="Input_inputContainer"]'); const inputContainer = origInputContainer.cloneNode(false); // input const origInput = origInputContainer.querySelector('input'); const input = origInput.cloneNode(false); input.value = ''; input.placeholder = '输入期望数量'; input.type = 'text'; input.maxLength = '12'; // === 新增:获得焦点时全选 === input.addEventListener('focus', function () { setTimeout(() => { input.select(); }, 0); }); // === 新增:回车触发原输入框回车 === input.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.keyCode === 13) { if (origInput) { const event = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13, which: 13 }); origInput.dispatchEvent(event); } } }); inputContainer.appendChild(input); inputWrap.appendChild(inputContainer); newBlock.appendChild(label); newBlock.appendChild(inputWrap); return {newBlock, input, origBlock}; // 返回origBlock用于按钮层级 } function parseTimes(val) { if (val === '∞' || val === '' || val === undefined || val === null) return Infinity; return parseInt(val.replace(/[^0-9]/g, ''), 10) || 0; } function parseQty(val) { if (val === '' || val === undefined || val === null) return 0; if (val === '∞') return Infinity; return parseInt(val.replace(/[^0-9]/g, ''), 10) || 0; } function insertQuantityInput() { const skillType = getCurrentSkillType(); // 仅处理五种情况,其余直接返回 if (!['milking', 'foraging', 'woodcutting', 'cooking', 'brewing'].includes(skillType)) return; if (skillType === 'foraging'){ const actionName = getCurrentActionName(); if (actionName === '翠野农场' || actionName === '波光湖泊' || actionName === '迷雾森林' || actionName === '深紫沙滩' || actionName === '傻牛山谷' || actionName === '奥林匹斯山' || actionName === '小行星带'){ return null; } } const origBlock = document.querySelector('[class^="SkillActionDetail_maxActionCountInput"]'); if (!origBlock) return; // 已经有“数量”栏则不再插入 if ([...origBlock.parentNode.children].some(e => { const lab = e.querySelector && e.querySelector('[class^="SkillActionDetail_label"]'); return lab && lab.textContent.trim() === '数量'; })) return; // 构造“数量”栏 const {newBlock, input: qtyInput} = createQuantityInputBlock(); if (!newBlock || !qtyInput) return; // 快捷按钮值与显示文本 const btns = [ {val: 100, txt: '100'}, {val: 300, txt: '300'}, {val: 500, txt: '500'}, {val: 1000, txt: '1k'}, {val: 2000, txt: '2k'} ]; // 原始按钮 const origButtons = origBlock.querySelectorAll('button'); let buttonClass = ''; if (origButtons.length > 0) buttonClass = origButtons[0].className; // 按钮栏创建在顶层 btns.forEach(({val, txt}) => { const btn = document.createElement('button'); btn.className = buttonClass; btn.textContent = txt; btn.addEventListener('click', () => { reactInputTriggerHack(qtyInput, val.toString()); }); newBlock.appendChild(btn); // 直接在SkillActionDetail_maxActionCountInput同级 }); // 插入到原始栏后 origBlock.parentNode.insertBefore(newBlock, origBlock.nextSibling); // 获取“次数”输入框和按钮 const timesInput = origBlock.querySelector('input'); const buttons = origBlock.querySelectorAll('button'); // 联动循环保护 let linking = false; // “次数”栏内容变化时,更新“数量” function updateQtyFromTimes() { if (linking) return; linking = true; let times = timesInput.value; if (times === '∞' || times === '' || times === undefined || times === null) { qtyInput.value = '∞'; linking = false; return; } times = parseTimes(times); if (!isFinite(times) || times <= 0) { qtyInput.value = ''; linking = false; return; } const {expected} = getTotalRangeFromTimes(times); if (!isFinite(expected)) { qtyInput.value = '∞'; } else { qtyInput.value = Math.round(expected); } linking = false; } // “数量”栏变化时,更新“次数” function updateTimesFromQty() { if (linking) return; linking = true; let qty = qtyInput.value; if (qty === '∞' || qty === '' || qty === undefined || qty === null) { reactInputTriggerHack(timesInput, '∞'); linking = false; return; } qty = parseQty(qty); if (!isFinite(qty) || qty <= 0) { reactInputTriggerHack(timesInput, ''); linking = false; return; } const times = Math.max(getTimesFromQty(qty), 1); reactInputTriggerHack(timesInput, times.toString()); linking = false; } // “次数”输入框联动 timesInput.addEventListener('input', updateQtyFromTimes); // “数量”输入框联动 qtyInput.addEventListener('input', updateTimesFromQty); // 按钮联动监听 for (const btn of buttons) { btn.addEventListener('click', () => { setTimeout(() => { updateQtyFromTimes(); }, 20); }); } // 初次填充 setTimeout(updateQtyFromTimes, 120); } // 监听页面变化 function observePanel() { let lastPanel = null; const observer = new MutationObserver(() => { const panel = document.querySelector('[class^="SkillActionDetail_content"]'); if (panel && panel !== lastPanel) { lastPanel = panel; setTimeout(insertQuantityInput, 100); } }); observer.observe(document.body, { childList: true, subtree: true }); } observePanel(); setTimeout(insertQuantityInput, 500); })();