MWI工具集
À partir de
// ==UserScript== // @name MWI_Toolkit // @namespace http://tampermonkey.net/ // @version 5.0.4 // @description MWI工具集 // @author zqzhang1996 // @match https://www.milkywayidle.com/* // @match https://test.milkywayidle.com/* // @require https://cdn.jsdelivr.net/npm/[email protected]/libs/lz-string.min.js // @grant GM_setValue // @grant GM_getValue // @run-at document-body // @license MIT // ==/UserScript== (function () { 'use strict'; //#region Calculator class TargetItemCategory { constructor(categoryHrid, needCalc = true) { this.needCalc = true; this.categoryDetailsElement = null; this.categorySummaryElement = null; this.needCalcCheckbox = null; this.categoryHrid = categoryHrid; this.needCalc = needCalc; } updateDisplayElement() { this.categoryDetailsElement.style.background = this.needCalc ? '#0E4F32' : '#2c2e45'; this.categorySummaryElement.style.background = this.needCalc ? '#147147' : '#393a5b'; this.needCalcCheckbox.checked = this.needCalc; } } class DisplayItem { constructor(itemHrid, count) { this.itemHrid = itemHrid; this.count = count; this.initDisplayProperties(); } initDisplayProperties() { if (Object.values(MWI_Toolkit_ActionDetailPlus.processableItemList).includes(this.itemHrid)) { this.categoryHrid = '/item_categories/materials'; } else { this.categoryHrid = MWI_Toolkit.initClientData?.itemDetailMap?.[this.itemHrid].categoryHrid; } this.displayName = MWI_Toolkit_I18n.getItemName(this.itemHrid); this.iconHref = MWI_Toolkit_Utils.getIconHrefByItemHrid(this.itemHrid); this.sortIndex = MWI_Toolkit_Utils.getSortIndexByItemHrid(this.itemHrid); } getOwnedCount() { return MWI_Toolkit_ItemsMap.getCount(this.itemHrid); } } class TargetItem extends DisplayItem { constructor(itemHrid, count, needCalc = true) { super(itemHrid, count); this.needCalc = true; this.displayElement = null; this.needCalcCheckbox = null; this.ownedSpan = null; this.targetInput = null; this.needCalc = needCalc; } updateDisplayElement() { if (this.needCalcCheckbox) { this.needCalcCheckbox.checked = this.needCalc; this.displayElement.style.background = this.needCalc ? '' : '#2c2e45'; } if (this.ownedSpan) { const newText = MWI_Toolkit_Utils.formatNumber(this.getOwnedCount()); if (this.ownedSpan.textContent !== newText) { this.ownedSpan.textContent = newText; } } if (this.targetInput) { const newText = this.count.toString(); if (this.targetInput.value.trim() === '' && this.count != 0) { this.targetInput.value = newText; } } } removeDisplayElement() { this.displayElement?.remove(); } } class TargetHouseRoom extends TargetItem { constructor(houseRoomHrid, level, needCalc = true) { super(houseRoomHrid, level, needCalc); } initDisplayProperties() { this.categoryHrid = '/item_categories/house_rooms'; this.displayName = MWI_Toolkit_I18n.getName(this.itemHrid, 'houseRoomNames'); this.iconHref = MWI_Toolkit_Utils.getIconHrefByHouseRoomHrid(this.itemHrid); this.sortIndex = MWI_Toolkit_Utils.getSortIndexByHouseRoomHrid(this.itemHrid); } getOwnedCount() { return MWI_Toolkit.gameObject?.state?.characterHouseRoomDict?.[this.itemHrid]?.level || 0; } } class RequiredItem extends DisplayItem { constructor(itemHrid, count, equivalentCount) { super(itemHrid, count); this.equivalentCount = 0; this.shortageDisplayElement = null; this.requiredDisplayElement = null; this.shortageSpan = null; this.ownedSpan = null; this.equivalentSpan = null; this.requiredSpan = null; this.requiredDiv = null; this.equivalentCount = equivalentCount; } getShortageCount() { return this.count - this.getOwnedCount() - this.equivalentCount; } updateDisplayElement() { const ownedCount = this.getOwnedCount(); const shortageCount = this.getShortageCount(); if (this.shortageSpan) { const newText = MWI_Toolkit_Utils.formatNumber(shortageCount); if (this.shortageSpan.textContent !== newText) { this.shortageSpan.textContent = newText; } this.shortageDisplayElement.style.display = shortageCount > 0 ? 'flex' : 'none'; } if (this.equivalentSpan) { const newText = MWI_Toolkit_Utils.formatNumber(this.equivalentCount) + '+'; if (this.equivalentSpan.textContent !== newText) { this.equivalentSpan.textContent = newText; } this.equivalentSpan.hidden = this.equivalentCount <= 0; } if (this.ownedSpan) { const newText = MWI_Toolkit_Utils.formatNumber(ownedCount) + '/'; if (this.ownedSpan.textContent !== newText) { this.ownedSpan.textContent = newText; } } if (this.requiredSpan) { const newText = MWI_Toolkit_Utils.formatNumber(this.count); if (this.requiredSpan.textContent !== newText) { this.requiredSpan.textContent = newText; } this.requiredDiv.style.color = shortageCount > 0 ? '#f44336' : '#E7E7E7'; } } removeDisplayElement() { this.shortageDisplayElement?.remove(); this.requiredDisplayElement?.remove(); } } class MWI_Toolkit_Calculator { static getStorageKey(characterID = null) { if (!characterID) { characterID = MWI_Toolkit?.gameObject?.state?.character?.id; } return `MWI_Toolkit_Calculator_TargetItems_${characterID}`; } // 保存目标物品 static saveTargetItems() { const storageKey = MWI_Toolkit_Calculator.getStorageKey(); const storageKey_Category = storageKey.replace('TargetItems', 'TargetItemCategories'); const dataToSave = [...MWI_Toolkit_Calculator.targetItemsMap.values()].map(item => ({ itemHrid: item.itemHrid, count: item.count, needCalc: item.needCalc })); const dataToSave_Category = [...MWI_Toolkit_Calculator.targetItemCategoryMap.values()].map(category => ({ categoryHrid: category.categoryHrid, needCalc: category.needCalc })); try { GM_setValue(storageKey, JSON.stringify(dataToSave)); GM_setValue(storageKey_Category, JSON.stringify(dataToSave_Category)); return true; } catch (error) { console.error('[MWI_Toolkit]' + error); return false; } } // 从特定角色ID加载数据 static loadTargetItems(characterID = null) { const storageKey = MWI_Toolkit_Calculator.getStorageKey(characterID); const storageKey_Category = storageKey.replace('TargetItems', 'TargetItemCategories'); try { const savedData = GM_getValue(storageKey, '[]'); const savedData_Category = GM_getValue(storageKey_Category, '[]'); const loadedItems = JSON.parse(savedData); const loadedCategories = JSON.parse(savedData_Category); // 验证并转换为Item实例 const validItemsMap = new Map(loadedItems.map((item) => { try { if (item.itemHrid.includes('/items/')) { return [item.itemHrid, new TargetItem(item.itemHrid, item.count, typeof item.needCalc === 'boolean' ? item.needCalc : true)]; } if (item.itemHrid.includes('/house_rooms/')) { return [item.itemHrid, new TargetHouseRoom(item.itemHrid, item.count, typeof item.needCalc === 'boolean' ? item.needCalc : true)]; } } catch { return null; } }).filter((item) => item !== null)); loadedCategories.forEach((category) => { if (MWI_Toolkit_Calculator.targetItemCategoryMap.has(category.categoryHrid)) { MWI_Toolkit_Calculator.targetItemCategoryMap.get(category.categoryHrid).needCalc = category.needCalc; } else { MWI_Toolkit_Calculator.targetItemCategoryMap.set(category.categoryHrid, new TargetItemCategory(category.categoryHrid, category.needCalc)); } }); if (validItemsMap.size > 0) { MWI_Toolkit_Calculator.clearAllTargetItems(); MWI_Toolkit_Calculator.targetItemsMap = validItemsMap; MWI_Toolkit_Calculator.renderItemsDisplay(); MWI_Toolkit_Calculator.saveTargetItems(); } } catch (error) { console.error('[MWI_Toolkit]' + error); } } // 更新目标物品 static updateTargetItem(itemHrid, count = 1) { if (!itemHrid) return; const item = MWI_Toolkit_Calculator.targetItemsMap.get(itemHrid); if (item) { item.count = count; } else { // 添加新物品 if (itemHrid.includes('/items/')) { MWI_Toolkit_Calculator.targetItemsMap.set(itemHrid, new TargetItem(itemHrid, count)); } if (itemHrid.includes('/house_rooms/')) { MWI_Toolkit_Calculator.targetItemsMap.set(itemHrid, new TargetHouseRoom(itemHrid, count)); } } MWI_Toolkit_Calculator.saveAndScheduleRender(); } // 删除目标物品 static removeTargetItem(itemHrid) { if (!itemHrid) return; MWI_Toolkit_Calculator.targetItemsMap.get(itemHrid)?.removeDisplayElement(); MWI_Toolkit_Calculator.targetItemsMap.delete(itemHrid); MWI_Toolkit_Calculator.saveAndScheduleRender(); } // 清空目标物品 static clearAllTargetItems() { MWI_Toolkit_Calculator.targetItemsMap.forEach(item => item.removeDisplayElement()); MWI_Toolkit_Calculator.targetItemsMap.clear(); MWI_Toolkit_Calculator.saveAndScheduleRender(); } // 保存数据并计划渲染 static saveAndScheduleRender() { // 保存数据到存储 MWI_Toolkit_Calculator.saveTargetItems(); MWI_Toolkit_Calculator.scheduleRender(); } // 计划延迟渲染 static scheduleRender() { // 清除之前的计时器 if (MWI_Toolkit_Calculator.renderTimeout) { clearTimeout(MWI_Toolkit_Calculator.renderTimeout); } // 设置新的计时器 MWI_Toolkit_Calculator.renderTimeout = setTimeout(() => { MWI_Toolkit_Calculator.renderItemsDisplay(); MWI_Toolkit_Calculator.renderTimeout = null; }, 300); // 300ms 防抖延迟 } // 递归计算所需材料 static calculateRequiredItems(targetItem) { if (targetItem.count === 0) return []; if (targetItem.itemHrid.includes('/house_rooms/')) { // 处理房屋房间逻辑 return this.calculateRequiredItemsForHouseRoom(targetItem.itemHrid, targetItem.count); } let requiredItems = new Array(); requiredItems.push({ itemHrid: targetItem.itemHrid, count: targetItem.count }); const actionTypes = ["cheesesmithing", "crafting", "tailoring", "cooking", "brewing"]; const itemName = targetItem.itemHrid.split('/').pop(); for (const actionType of actionTypes) { const actionHrid = `/actions/${actionType}/${itemName}`; if (MWI_Toolkit.initClientData?.actionDetailMap?.hasOwnProperty(actionHrid)) { const actionDetail = MWI_Toolkit.initClientData?.actionDetailMap[actionHrid]; const upgradeItemHrid = actionDetail.upgradeItemHrid; const inputItems = actionDetail.inputItems; let outputCount = 1; const outputItems = actionDetail.outputItems; if (outputItems && outputItems.length > 0) { const matchingOutput = outputItems.find(output => output.itemHrid === targetItem.itemHrid); if (matchingOutput) { outputCount = matchingOutput.count; } } const actionTypeDrinkSlots = MWI_Toolkit_ActionDetailPlus.getActionTypeDrinkSlots(actionType); // 检查工匠茶加成 let artisanBuff = 0; if (actionTypeDrinkSlots?.some(itemHrid => itemHrid === '/items/artisan_tea')) { artisanBuff = 0.1 * MWI_Toolkit_ActionDetailPlus.getDrinkConcentration(); } // 检查美食茶加成 let gourmetBuff = 0; if (actionTypeDrinkSlots?.some(itemHrid => itemHrid === '/items/gourmet_tea')) { gourmetBuff = 0.12 * MWI_Toolkit_ActionDetailPlus.getDrinkConcentration(); } // 递归计算输入材料 for (const input of inputItems) { const adjustedCount = input.count * targetItem.count / outputCount / (1 + gourmetBuff) * (1 - artisanBuff); requiredItems = MWI_Toolkit_Calculator.mergeItemArrays(requiredItems, MWI_Toolkit_Calculator.calculateRequiredItems({ itemHrid: input.itemHrid, count: adjustedCount })); } // 处理升级物品(不适用工匠茶加成) if (upgradeItemHrid) { requiredItems = MWI_Toolkit_Calculator.mergeItemArrays(requiredItems, MWI_Toolkit_Calculator.calculateRequiredItems({ itemHrid: upgradeItemHrid, count: targetItem.count / outputCount / (1 + gourmetBuff) })); } return requiredItems; } } // 添加地下城代币兑换材料计算 if (requiredItems.length === 1) { const shopHrid = `/shop_items/${itemName}`; if (MWI_Toolkit.initClientData?.shopItemDetailMap?.hasOwnProperty(shopHrid)) { const shopItemDetail = MWI_Toolkit.initClientData?.shopItemDetailMap[shopHrid]; if (shopItemDetail.category === "/shop_categories/dungeon") { shopItemDetail.costs.forEach(cost => { requiredItems.push({ itemHrid: cost.itemHrid, count: cost.count * targetItem.count }); }); } } } return requiredItems; } // 批量计算材料需求 static batchCalculateRequiredItems(targetItems) { let allRequiredItems = new Array(); for (const targetItem of targetItems) { const requiredItems = MWI_Toolkit_Calculator.calculateRequiredItems(targetItem); allRequiredItems = MWI_Toolkit_Calculator.mergeItemArrays(allRequiredItems, requiredItems); } return allRequiredItems; } // 计算房屋房间所需材料 static calculateRequiredItemsForHouseRoom(targetHouseRoomHrid, targetHouseRoomLevel) { let targetItems = Array(); const characterHouseRoomLevel = MWI_Toolkit.gameObject.state.characterHouseRoomDict?.[targetHouseRoomHrid]?.level || 0; const upgradeCostsMap = MWI_Toolkit.initClientData?.houseRoomDetailMap?.[targetHouseRoomHrid]?.upgradeCostsMap; for (let i = characterHouseRoomLevel + 1; i <= targetHouseRoomLevel && i <= 8; i++) { targetItems = targetItems.concat(upgradeCostsMap[i] || []); } return MWI_Toolkit_Calculator.batchCalculateRequiredItems(targetItems); } // 计算等效材料 static calculateEquivalentItems(requiredItems) { const ownedItems = new Array(); const ownedItemsNG = new Array(); for (const requiredItem of requiredItems) { const ownedCount = MWI_Toolkit_ItemsMap.getCount(requiredItem.itemHrid); // 负目标数量用于手动标记已有的等效物品 const targetItemNG = MWI_Toolkit_Calculator.targetItemsMap.get(requiredItem.itemHrid); const targetCountNG = Math.min((targetItemNG?.needCalc && targetItemNG?.count) ? targetItemNG.count : 0, 0); // 这里count取required和owned-equivalent中的较小值,用于抵消需求 ownedItems.push({ itemHrid: requiredItem.itemHrid, count: Math.min(requiredItem.count, ownedCount - targetCountNG) }); ownedItemsNG.push({ itemHrid: requiredItem.itemHrid, count: Math.min(requiredItem.count, ownedCount) * -1 }); } // 减掉原值得到等效值 return MWI_Toolkit_Calculator.mergeItemArrays(MWI_Toolkit_Calculator.batchCalculateRequiredItems(ownedItems), ownedItemsNG); } // 合并材料数组并按排序顺序返回 static mergeItemArrays(arr1, arr2) { const map = new Map(); for (const item of arr1.concat(arr2)) { if (map.has(item.itemHrid)) { if (item.itemHrid.includes('/items/')) { map.get(item.itemHrid).count += item.count; } if (item.itemHrid.includes('/house_rooms/')) { map.get(item.itemHrid).count = Math.max(map.get(item.itemHrid).count, item.count); } } else { map.set(item.itemHrid, { itemHrid: item.itemHrid, count: item.count }); } } // 排序 return Array.from(map.values()).sort((a, b) => MWI_Toolkit_Utils.getSortIndexByItemHrid(a.itemHrid) - MWI_Toolkit_Utils.getSortIndexByItemHrid(b.itemHrid)); } static initialize() { MWI_Toolkit_ItemsMap.itemsUpdatedCallbacks.push((enditemsMap) => { MWI_Toolkit_Calculator.scheduleRender(); }); } static initializeCalculatorUI() { MWI_Toolkit.waitForElement('[class^="CharacterManagement_tabsComponentContainer"] [class*="TabsComponent_tabsContainer"]', () => { MWI_Toolkit_Calculator.createCalculatorUI(); }); } // 初始化UI static createCalculatorUI() { // 已有标签页则不重复初始化 if (document.querySelector('[class^="Toolkit_Calculator_Container"]')) { return; } // 获取容器 const tabsContainer = document.querySelector('[class^="CharacterManagement_tabsComponentContainer"] [class*="TabsComponent_tabsContainer"]'); const tabPanelsContainer = document.querySelector('[class^="CharacterManagement_tabsComponentContainer"] [class*="TabsComponent_tabPanelsContainer"]'); if (!tabsContainer || !tabPanelsContainer) { console.error('[MWI_Toolkit_Calculator] 无法找到标签页容器'); return; } MWI_Toolkit_Calculator.createCalculatorTab(tabsContainer, tabPanelsContainer); MWI_Toolkit_Calculator.targetItemsMap = new Map(); MWI_Toolkit_Calculator.requiredItemsMap = new Map(); // 加载保存数据 MWI_Toolkit_Calculator.loadTargetItems(); console.log('[MWI_Toolkit_Calculator] UI初始化完成'); } // 创建MWI计算器标签页 static createCalculatorTab(tabsContainer, tabPanelsContainer) { // 新增"MWI计算器"按钮 const oldTabButtons = tabsContainer.querySelectorAll("button"); MWI_Toolkit_Calculator.tabButton = oldTabButtons[1].cloneNode(true); MWI_Toolkit_Calculator.tabButton.children[0].textContent = (MWI_Toolkit.getGameLanguage() === 'zh') ? 'MWI计算器' : 'MWI_Calculator'; oldTabButtons[0].parentElement.appendChild(MWI_Toolkit_Calculator.tabButton); // 新增MWI计算器tabPanel const oldTabPanels = tabPanelsContainer.querySelectorAll('[class*="TabPanel_tabPanel"]'); MWI_Toolkit_Calculator.tabPanel = oldTabPanels[1].cloneNode(false); oldTabPanels[0].parentElement.appendChild(MWI_Toolkit_Calculator.tabPanel); MWI_Toolkit_Calculator.bindCalculatorTabEvents(oldTabButtons, oldTabPanels); // 创建计算器面板 const calculatorPanel = MWI_Toolkit_Calculator.createCalculatorPanel(); MWI_Toolkit_Calculator.tabPanel.appendChild(calculatorPanel); } // 绑定标签页事件 static bindCalculatorTabEvents(oldTabButtons, oldTabPanels) { for (let i = 0; i < oldTabButtons.length; i++) { oldTabButtons[i].addEventListener('click', () => { MWI_Toolkit_Calculator.tabPanel.hidden = true; // 强制隐藏 MWI_Toolkit_Calculator.tabPanel.classList.add('TabPanel_hidden__26UM3'); MWI_Toolkit_Calculator.tabButton.classList.remove('Mui-selected'); MWI_Toolkit_Calculator.tabButton.setAttribute('aria-selected', 'false'); MWI_Toolkit_Calculator.tabButton.tabIndex = -1; oldTabButtons[i].classList.add('Mui-selected'); oldTabButtons[i].setAttribute('aria-selected', 'true'); oldTabButtons[i].tabIndex = 0; oldTabPanels[i].classList.remove('TabPanel_hidden__26UM3'); oldTabPanels[i].hidden = false; // 显示目标 }, true); } MWI_Toolkit_Calculator.tabButton.addEventListener('click', () => { oldTabButtons.forEach(btn => { btn.classList.remove('Mui-selected'); btn.setAttribute('aria-selected', 'false'); btn.tabIndex = -1; }); oldTabPanels.forEach(panel => { panel.hidden = true; // 强制隐藏 panel.classList.add('TabPanel_hidden__26UM3'); }); MWI_Toolkit_Calculator.tabButton.classList.add('Mui-selected'); MWI_Toolkit_Calculator.tabButton.setAttribute('aria-selected', 'true'); MWI_Toolkit_Calculator.tabButton.tabIndex = 0; MWI_Toolkit_Calculator.tabPanel.classList.remove('TabPanel_hidden__26UM3'); MWI_Toolkit_Calculator.tabPanel.hidden = false; // 显示目标 }, true); } // 创建计算器面板 static createCalculatorPanel() { const calculatorPanel = document.createElement('div'); calculatorPanel.className = 'Toolkit_Calculator_Container'; // 创建物品搜索区域 const addItemSection = MWI_Toolkit_Calculator.createAddItemSection(); calculatorPanel.appendChild(addItemSection); // 左侧区域 const leftDiv = document.createElement('div'); leftDiv.style.display = 'inline-block'; leftDiv.style.verticalAlign = 'top'; leftDiv.style.width = '60%'; leftDiv.style.padding = '0px 2px'; MWI_Toolkit_Calculator.createItemDetailsMap(leftDiv, MWI_Toolkit_Calculator.targetItemDetailsMap); calculatorPanel.appendChild(leftDiv); MWI_Toolkit_Calculator.targetItemDetailsMap.forEach((details, categoryHrid) => { const summary = details.querySelector('summary'); const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.style.verticalAlign = 'middle'; if (!MWI_Toolkit_Calculator.targetItemCategoryMap.has(categoryHrid)) { MWI_Toolkit_Calculator.targetItemCategoryMap.set(categoryHrid, new TargetItemCategory(categoryHrid)); } MWI_Toolkit_Calculator.targetItemCategoryMap.get(categoryHrid).categoryDetailsElement = details; MWI_Toolkit_Calculator.targetItemCategoryMap.get(categoryHrid).categorySummaryElement = summary; MWI_Toolkit_Calculator.targetItemCategoryMap.get(categoryHrid).needCalcCheckbox = checkbox; checkbox.checked = MWI_Toolkit_Calculator.targetItemCategoryMap.get(categoryHrid).needCalc; checkbox.addEventListener('change', () => { MWI_Toolkit_Calculator.targetItemCategoryMap.get(categoryHrid).needCalc = checkbox.checked; MWI_Toolkit_Calculator.saveAndScheduleRender(); }); summary.prepend(checkbox); }); // 右侧区域 const rightDiv = document.createElement('div'); rightDiv.style.display = 'inline-block'; rightDiv.style.verticalAlign = 'top'; rightDiv.style.width = '40%'; rightDiv.style.padding = '0px 2px'; const shortageItemDetails = document.createElement('details'); shortageItemDetails.style.background = '#902f10'; shortageItemDetails.style.borderRadius = '4px'; shortageItemDetails.style.padding = '2px'; shortageItemDetails.open = true; const shortageSummary = document.createElement('summary'); shortageSummary.textContent = MWI_Toolkit.getGameLanguage() === 'zh' ? '缺口' : 'Shortages'; shortageSummary.style.background = '#af3914'; shortageSummary.style.borderRadius = '4px'; shortageSummary.style.fontSize = '14px'; shortageSummary.style.padding = '2px 6px'; shortageSummary.style.textAlign = 'left'; shortageSummary.style.cursor = 'pointer'; shortageItemDetails.appendChild(shortageSummary); MWI_Toolkit_Calculator.createItemDetailsMap(shortageItemDetails, MWI_Toolkit_Calculator.shortageItemDetailsMap); rightDiv.appendChild(shortageItemDetails); const requiredItemDetails = document.createElement('details'); requiredItemDetails.style.background = '#0c385a'; requiredItemDetails.style.borderRadius = '4px'; requiredItemDetails.style.padding = '2px'; const requiredSummary = document.createElement('summary'); requiredSummary.textContent = MWI_Toolkit.getGameLanguage() === 'zh' ? '详情(等效+库存/需求)' : 'Status(Equivalent+Owned/Required)'; requiredSummary.style.background = '#1770b3'; requiredSummary.style.borderRadius = '4px'; requiredSummary.style.fontSize = '14px'; requiredSummary.style.padding = '2px 6px'; requiredSummary.style.textAlign = 'left'; requiredSummary.style.cursor = 'pointer'; requiredItemDetails.appendChild(requiredSummary); MWI_Toolkit_Calculator.createItemDetailsMap(requiredItemDetails, MWI_Toolkit_Calculator.requiredItemDetailsMap); rightDiv.appendChild(requiredItemDetails); calculatorPanel.appendChild(rightDiv); return calculatorPanel; } // 创建物品分类区域 static createItemDetailsMap(container, ItemDetailsMap) { MWI_Toolkit_Calculator.itemCategoryList.forEach(categoryHrid => { const details = document.createElement('details'); details.style.background = '#2c2e45'; details.style.borderRadius = '4px'; details.style.margin = '2px 0px'; details.open = true; const summary = document.createElement('summary'); summary.textContent = MWI_Toolkit_I18n.getName(categoryHrid, 'itemCategoryNames'); summary.style.background = '#393a5b'; summary.style.borderRadius = '4px'; summary.style.fontSize = '14px'; summary.style.padding = '2px 6px'; summary.style.textAlign = 'left'; summary.style.cursor = 'pointer'; details.appendChild(summary); container.appendChild(details); ItemDetailsMap.set(categoryHrid, details); }); } // 创建添加物品区域 static createAddItemSection() { const addItemSection = document.createElement('div'); // 左侧60%:物品搜索区域 const leftSection = document.createElement('div'); leftSection.style.display = 'inline-block'; leftSection.style.verticalAlign = 'top'; leftSection.style.width = '60%'; const searchContainer = MWI_Toolkit_Calculator.createItemSearchComponent(); leftSection.appendChild(searchContainer); // 右侧40%:房屋选择区域 const rightSection = document.createElement('div'); rightSection.style.display = 'inline-block'; rightSection.style.verticalAlign = 'top'; rightSection.style.width = '40%'; const houseContainer = MWI_Toolkit_Calculator.createHouseRoomSelectionComponent(); rightSection.appendChild(houseContainer); addItemSection.appendChild(leftSection); addItemSection.appendChild(rightSection); return addItemSection; } // 创建物品搜索组件 static createItemSearchComponent() { const itemSearchComponent = document.createElement('div'); itemSearchComponent.style.background = '#2c2e45'; itemSearchComponent.style.border = 'none'; itemSearchComponent.style.borderRadius = '4px'; itemSearchComponent.style.padding = '4px'; itemSearchComponent.style.margin = '2px'; itemSearchComponent.style.display = 'flex'; itemSearchComponent.style.position = 'relative'; // 物品搜索输入框 const itemSearchInput = document.createElement('input'); itemSearchInput.type = 'text'; itemSearchInput.placeholder = (MWI_Toolkit.getGameLanguage() === 'zh') ? '搜索物品名称...' : 'Search item name...'; itemSearchInput.style.background = '#dde2f8'; itemSearchInput.style.color = '#000000'; itemSearchInput.style.border = 'none'; itemSearchInput.style.borderRadius = '4px'; itemSearchInput.style.padding = '4px'; itemSearchInput.style.margin = '2px'; itemSearchInput.style.minWidth = '40px'; itemSearchInput.style.flex = '1'; // 搜索结果下拉列表 const searchResults = document.createElement('div'); searchResults.style.background = '#2c2e45'; searchResults.style.border = 'none'; searchResults.style.borderRadius = '4px'; searchResults.style.padding = '4px'; searchResults.style.margin = '2px'; searchResults.style.width = '200px'; searchResults.style.maxHeight = '335px'; searchResults.style.overflowY = 'auto'; searchResults.style.zIndex = '1000'; searchResults.style.display = 'none'; searchResults.style.position = 'absolute'; searchResults.style.left = '4px'; searchResults.style.top = '32px'; // 数量输入框 const countInput = document.createElement('input'); countInput.type = 'text'; countInput.value = '1'; countInput.placeholder = (MWI_Toolkit.getGameLanguage() === 'zh') ? '数量' : 'Count'; countInput.style.background = '#dde2f8'; countInput.style.color = '#000000'; countInput.style.border = 'none'; countInput.style.borderRadius = '4px'; countInput.style.padding = '4px'; countInput.style.margin = '2px'; countInput.style.width = '60px'; // 添加按钮 const addButton = document.createElement('button'); addButton.textContent = (MWI_Toolkit.getGameLanguage() === 'zh') ? '添加' : 'Add'; addButton.style.background = '#4CAF50'; addButton.style.color = '#FFFFFF'; addButton.style.border = 'none'; addButton.style.borderRadius = '4px'; addButton.style.padding = '4px'; addButton.style.margin = '2px'; addButton.style.cursor = 'pointer'; // 清空按钮 const clearAllButton = document.createElement('button'); clearAllButton.textContent = (MWI_Toolkit.getGameLanguage() === 'zh') ? '清空' : 'Clear'; clearAllButton.style.background = '#f44336'; clearAllButton.style.color = '#FFFFFF'; clearAllButton.style.border = 'none'; clearAllButton.style.borderRadius = '4px'; clearAllButton.style.padding = '4px'; clearAllButton.style.margin = '2px'; clearAllButton.style.cursor = 'pointer'; // 绑定搜索事件 MWI_Toolkit_Calculator.bindItemSearchComponentEvents(itemSearchInput, countInput, searchResults, addButton, clearAllButton); itemSearchComponent.appendChild(itemSearchInput); itemSearchComponent.appendChild(countInput); itemSearchComponent.appendChild(addButton); itemSearchComponent.appendChild(clearAllButton); itemSearchComponent.appendChild(searchResults); return itemSearchComponent; } // 绑定搜索相关事件 static bindItemSearchComponentEvents(itemSearchInput, countInput, searchResults, addButton, clearAllButton) { // 输入框获得焦点时全选内容 itemSearchInput.addEventListener('focus', () => { setTimeout(() => { itemSearchInput.select(); }, 0); }); // 搜索功能 itemSearchInput.addEventListener('input', () => { const searchTerm = itemSearchInput.value.toLowerCase().trim(); if (searchTerm.length < 2) { searchResults.style.display = 'none'; return; } // 获取并过滤物品 const itemDetailMap = MWI_Toolkit?.initClientData?.itemDetailMap; if (!itemDetailMap) return; const filteredItems = Object.keys(itemDetailMap) .filter(itemHrid => { return MWI_Toolkit_I18n.getItemName(itemHrid).toLowerCase().includes(searchTerm); }) .sort((a, b) => { const sortIndexA = MWI_Toolkit_Utils.getSortIndexByItemHrid(a); const sortIndexB = MWI_Toolkit_Utils.getSortIndexByItemHrid(b); return sortIndexA - sortIndexB; }); if (filteredItems.length === 0) { searchResults.style.display = 'none'; return; } MWI_Toolkit_Calculator.populateSearchResults(searchResults, filteredItems, (itemHrid) => { itemSearchInput.value = MWI_Toolkit_I18n.getItemName(itemHrid); searchResults.style.display = 'none'; }); searchResults.style.display = 'block'; }); // 键盘操作 itemSearchInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); MWI_Toolkit_Calculator.addItemAndResetItemSearchComponent(itemSearchInput, countInput, searchResults); } else if (e.key === 'Escape') { searchResults.style.display = 'none'; } }); // 输入框获得焦点时全选内容 countInput.addEventListener('focus', () => { setTimeout(() => { countInput.select(); }, 0); }); // 仅允许输入数字 countInput.addEventListener('input', () => { // 只允许负号在首位,其余为数字 countInput.value = countInput.value.replace(/(?!^)-|[^\d-]/g, ''); }); // 键盘操作 countInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); MWI_Toolkit_Calculator.addItemAndResetItemSearchComponent(itemSearchInput, countInput, searchResults); } else if (e.key === 'Escape') { searchResults.style.display = 'none'; } }); // 添加按钮事件 addButton.addEventListener('click', () => { MWI_Toolkit_Calculator.addItemAndResetItemSearchComponent(itemSearchInput, countInput, searchResults); }); // 清空按钮事件 clearAllButton.addEventListener('click', () => { if (confirm((MWI_Toolkit.getGameLanguage() === 'zh') ? '确定要清空所有目标物品吗?' : 'Are you sure you want to clear all target items?')) { // 通过事件处理器清空 MWI_Toolkit_Calculator.clearAllTargetItems(); } }); // 点击其他地方隐藏搜索结果 document.addEventListener('click', () => { searchResults.style.display = 'none'; }); } // 填充搜索结果 static populateSearchResults(searchResults, filteredItems, onItemSelect) { searchResults.innerHTML = ''; filteredItems.forEach((itemHrid, index) => { const resultItem = document.createElement('div'); resultItem.style.borderBottom = '1px solid #98a7e9'; resultItem.style.borderRadius = '4px'; resultItem.style.padding = '4px'; resultItem.style.alignItems = 'center'; resultItem.style.display = 'flex'; resultItem.style.cursor = 'pointer'; if (index === 0) { resultItem.style.background = '#4a4c6a'; } // 物品图标 const itemIcon = document.createElement('div'); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '16px'); svg.setAttribute('height', '16px'); svg.style.display = 'block'; const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', MWI_Toolkit_Utils.getIconHrefByItemHrid(itemHrid)); svg.appendChild(use); itemIcon.appendChild(svg); // 物品名称 const itemName = document.createElement('span'); itemName.textContent = MWI_Toolkit_I18n.getItemName(itemHrid); itemName.style.marginLeft = '2px'; resultItem.appendChild(itemIcon); resultItem.appendChild(itemName); // 悬停高亮 resultItem.addEventListener('mouseenter', () => { resultItem.style.background = '#4a4c6a'; }); resultItem.addEventListener('mouseleave', () => { resultItem.style.background = 'transparent'; }); resultItem.addEventListener('click', () => onItemSelect(itemHrid)); searchResults.appendChild(resultItem); }); } // 添加物品并重置搜索组件(包含itemHrid获取和判空) static addItemAndResetItemSearchComponent(itemSearchInput, countInput, searchResults) { const InputValue = itemSearchInput.value.trim(); // 如果InputValue是纯数字,则视为从特定角色加载数据 if (/^\d+$/.test(InputValue)) { const characterID = parseInt(InputValue, 10); MWI_Toolkit_Calculator.loadTargetItems(characterID); return; } const itemHrid = MWI_Toolkit_I18n.getItemHridByName(InputValue); if (!itemHrid) return; const count = parseInt(countInput.value, 10) || 1; MWI_Toolkit_Calculator.updateTargetItem(itemHrid, count); itemSearchInput.value = ''; countInput.value = '1'; searchResults.style.display = 'none'; } // 创建房屋选择区域 static createHouseRoomSelectionComponent() { const HouseRoomSelectionComponent = document.createElement('div'); HouseRoomSelectionComponent.style.background = '#2c2e45'; HouseRoomSelectionComponent.style.border = 'none'; HouseRoomSelectionComponent.style.borderRadius = '4px'; HouseRoomSelectionComponent.style.padding = '4px'; HouseRoomSelectionComponent.style.margin = '2px'; HouseRoomSelectionComponent.style.display = 'flex'; // 下拉菜单 const dropdown = MWI_Toolkit_Calculator.createHouseRoomTypeDropdown(); // 等级输入框 const levelInput = document.createElement('input'); levelInput.type = 'number'; levelInput.min = '1'; levelInput.max = '8'; levelInput.step = '1'; levelInput.value = '1'; levelInput.placeholder = (MWI_Toolkit.getGameLanguage() === 'zh') ? '等级' : 'Level'; levelInput.style.background = '#dde2f8'; levelInput.style.color = '#000000'; levelInput.style.border = 'none'; levelInput.style.borderRadius = '4px'; levelInput.style.padding = '4px'; levelInput.style.margin = '2px'; levelInput.style.width = '35px'; // 添加按钮 const addListButton = document.createElement('button'); addListButton.textContent = (MWI_Toolkit.getGameLanguage() === 'zh') ? '添加' : 'Add'; addListButton.style.background = '#4CAF50'; addListButton.style.color = '#FFFFFF'; addListButton.style.border = 'none'; addListButton.style.borderRadius = '4px'; addListButton.style.padding = '4px'; addListButton.style.margin = '2px'; addListButton.style.width = '35px'; addListButton.style.cursor = 'pointer'; // 绑定事件 MWI_Toolkit_Calculator.bindHouseRoomSelectionComponentEvents(dropdown, levelInput, addListButton); HouseRoomSelectionComponent.appendChild(dropdown); HouseRoomSelectionComponent.appendChild(levelInput); HouseRoomSelectionComponent.appendChild(addListButton); return HouseRoomSelectionComponent; } // 创建房屋类型下拉菜单 static createHouseRoomTypeDropdown() { // 创建容器 const dropdown = document.createElement('div'); dropdown.style.display = 'flex'; dropdown.style.minWidth = '20px'; dropdown.style.flex = '1'; dropdown.style.position = 'relative'; // 选中项显示区 const selected = document.createElement('div'); selected.style.background = '#393a5b'; selected.style.color = '#000000'; selected.style.border = 'none'; selected.style.borderRadius = '4px'; selected.style.paddingLeft = '4px'; selected.style.margin = '2px'; selected.style.minWidth = '40px'; selected.style.flex = '1'; selected.style.cursor = 'pointer'; selected.style.display = 'flex'; selected.style.alignItems = 'center'; // 下拉菜单列表 const list = document.createElement('div'); list.style.background = '#2c2e45'; list.style.border = 'none'; list.style.borderRadius = '4px'; list.style.padding = '4px'; list.style.margin = '2px'; list.style.width = '150px'; list.style.maxHeight = '335px'; list.style.overflowY = 'auto'; list.style.zIndex = '1000'; list.style.display = 'none'; list.style.position = 'absolute'; list.style.left = '0px'; list.style.top = '32px'; const HouseRoomTypeOptions = MWI_Toolkit_Calculator.createHouseRoomTypeOptions(selected, dropdown); HouseRoomTypeOptions.forEach(optionItem => { list.appendChild(optionItem); }); HouseRoomTypeOptions[0] && HouseRoomTypeOptions[0].click(); // 默认选中第一个 dropdown.appendChild(selected); dropdown.appendChild(list); // 点击展开/收起 selected.addEventListener('click', (e) => { e.stopPropagation(); list.style.display = list.style.display === 'block' ? 'none' : 'block'; }); // 点击外部关闭 document.addEventListener('click', () => { list.style.display = 'none'; }); return dropdown; } // 创建房屋类型选项 static createHouseRoomTypeOptions(selected, dropdown) { const houseRoomDetailMap = MWI_Toolkit.initClientData?.houseRoomDetailMap; if (!houseRoomDetailMap) { return []; } return Object.values(houseRoomDetailMap) .sort((a, b) => (a.sortIndex ?? 9999) - (b.sortIndex ?? 9999)) .map(houseRoomDetail => { const optionItem = document.createElement('div'); optionItem.style.borderBottom = '1px solid #98a7e9'; optionItem.style.borderRadius = '4px'; optionItem.style.padding = '4px'; optionItem.style.alignItems = 'center'; optionItem.style.display = 'flex'; optionItem.style.cursor = 'pointer'; // 房屋房间图标 const houseRoomIcon = document.createElement('div'); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '16px'); svg.setAttribute('height', '16px'); svg.style.display = 'block'; const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', MWI_Toolkit_Utils.getIconHrefBySkillHrid(houseRoomDetail.skillHrid)); svg.appendChild(use); houseRoomIcon.appendChild(svg); // 房屋房间名称 const houseRoomName = document.createElement('span'); houseRoomName.textContent = MWI_Toolkit_I18n?.getName(houseRoomDetail.hrid, "houseRoomNames") || houseRoomDetail.hrid; houseRoomName.style.marginLeft = '2px'; houseRoomName.style.whiteSpace = 'nowrap'; houseRoomName.style.overflow = 'hidden'; optionItem.appendChild(houseRoomIcon); optionItem.appendChild(houseRoomName); optionItem.addEventListener('click', () => { selected.innerHTML = ''; const selectedIcon = houseRoomIcon.cloneNode(true); selected.appendChild(selectedIcon); const selectedName = houseRoomName.cloneNode(true); selectedName.style.color = '#FFFFFF'; selected.appendChild(selectedName); dropdown.dataset.houseRoomHrid = houseRoomDetail.hrid; optionItem.parentElement.style.display = 'none'; }); // 悬停高亮 optionItem.addEventListener('mouseenter', () => { optionItem.style.background = '#4a4c6a'; }); optionItem.addEventListener('mouseleave', () => { optionItem.style.background = 'transparent'; }); optionItem.dataset.houseRoomHrid = houseRoomDetail.hrid; return optionItem; }); } // 绑定房屋选择相关事件 static bindHouseRoomSelectionComponentEvents(dropdown, levelInput, addListButton) { // 输入框获得焦点时全选内容 levelInput.addEventListener('focus', function () { setTimeout(() => { levelInput.select(); }, 0); }); // 添加按钮事件 addListButton.addEventListener('click', () => { const houseRoomHrid = dropdown.dataset.houseRoomHrid; const level = parseInt(levelInput.value) || 1; MWI_Toolkit_Calculator.updateTargetItem(houseRoomHrid, level); }); } // 渲染物品列表 static renderItemsDisplay() { MWI_Toolkit_Calculator.targetItemCategoryMap.forEach((category) => { category.updateDisplayElement(); }); // 这里只需要新增或更新,删除在targetItems变动时进行处理 MWI_Toolkit_Calculator.itemCategoryList.forEach(categoryHrid => { const details = MWI_Toolkit_Calculator.targetItemDetailsMap.get(categoryHrid); let lastElement = details.querySelector('summary'); let itemCount = 0; [...MWI_Toolkit_Calculator.targetItemsMap.values()] .sort((a, b) => a.sortIndex - b.sortIndex) .forEach(targetItem => { if (targetItem.categoryHrid !== categoryHrid) { return; } if (!targetItem.displayElement) { MWI_Toolkit_Calculator.createTargetItemDisplayElement(targetItem); lastElement.insertAdjacentElement('afterend', targetItem.displayElement); } targetItem.updateDisplayElement(); lastElement = targetItem.displayElement; itemCount++; }); details.hidden = itemCount === 0; }); const targetItemsToCalc = [...MWI_Toolkit_Calculator.targetItemsMap.values()] .sort((a, b) => a.sortIndex - b.sortIndex) .filter(item => item.needCalc && item.count > 0 && (MWI_Toolkit_Calculator.targetItemCategoryMap.get(item.categoryHrid)?.needCalc ?? true)) .map(item => ({ itemHrid: item.itemHrid, count: item.count })); // 计算需求物品显示数据 // batchCalculateRequiredItems返回的requiredItems已经是有序的 const requiredItems = MWI_Toolkit_Calculator.batchCalculateRequiredItems(targetItemsToCalc); const equivalentItems = MWI_Toolkit_Calculator.calculateEquivalentItems(requiredItems); // 移除不存在的物品 [...MWI_Toolkit_Calculator.requiredItemsMap.keys()].forEach(itemHrid => { if (!requiredItems.find(ri => ri.itemHrid === itemHrid)) { MWI_Toolkit_Calculator.requiredItemsMap.get(itemHrid)?.removeDisplayElement(); MWI_Toolkit_Calculator.requiredItemsMap.delete(itemHrid); } }); requiredItems.forEach(requiredItem => { const equivalentCount = equivalentItems.find(ei => ei.itemHrid === requiredItem.itemHrid)?.count || 0; const item = MWI_Toolkit_Calculator.requiredItemsMap.get(requiredItem.itemHrid); if (item) { item.count = requiredItem.count; item.equivalentCount = equivalentCount; } else { MWI_Toolkit_Calculator.requiredItemsMap.set(requiredItem.itemHrid, new RequiredItem(requiredItem.itemHrid, requiredItem.count, equivalentCount)); } }); MWI_Toolkit_Calculator.itemCategoryList.forEach(categoryHrid => { const shortageDetails = MWI_Toolkit_Calculator.shortageItemDetailsMap.get(categoryHrid); const requiredDetails = MWI_Toolkit_Calculator.requiredItemDetailsMap.get(categoryHrid); let lastShortageElement = shortageDetails.querySelector('summary'); let lastRequiredElement = requiredDetails.querySelector('summary'); let shortageItemCount = 0; let requiredItemCount = 0; [...MWI_Toolkit_Calculator.requiredItemsMap.values()] .sort((a, b) => a.sortIndex - b.sortIndex) .forEach(requiredItem => { if (requiredItem.categoryHrid !== categoryHrid) { return; } if (!requiredItem.shortageDisplayElement) { MWI_Toolkit_Calculator.createShortageItemDisplayElement(requiredItem); lastShortageElement.insertAdjacentElement('afterend', requiredItem.shortageDisplayElement); } if (!requiredItem.requiredDisplayElement) { MWI_Toolkit_Calculator.createRequiredItemDisplayElement(requiredItem); lastRequiredElement.insertAdjacentElement('afterend', requiredItem.requiredDisplayElement); } requiredItem.updateDisplayElement(); lastShortageElement = requiredItem.shortageDisplayElement; lastRequiredElement = requiredItem.requiredDisplayElement; shortageItemCount += requiredItem.getShortageCount() > 0 ? 1 : 0; requiredItemCount++; }); shortageDetails.hidden = shortageItemCount === 0; requiredDetails.hidden = requiredItemCount === 0; }); } // 创建物品容器(图标+名称) static createItemContainer(displayItem) { const container = document.createElement('div'); // container.style.background = '#393a5b'; // container.style.border = '1px solid'; // container.style.borderRadius = '4px'; // container.style.height = '21px'; container.style.minWidth = '40px'; container.style.alignItems = 'center'; container.style.display = 'flex'; // 物品图标 const iconContainer = document.createElement('div'); iconContainer.style.marginLeft = '2px'; const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '18px'); svg.setAttribute('height', '18px'); svg.style.display = 'block'; const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', displayItem.iconHref); svg.appendChild(use); iconContainer.appendChild(svg); // 物品名称 const displayNameDiv = document.createElement('div'); displayNameDiv.textContent = displayItem.displayName; displayNameDiv.style.padding = "4px 1px"; displayNameDiv.style.marginLeft = '2px'; displayNameDiv.style.whiteSpace = 'nowrap'; displayNameDiv.style.overflow = 'hidden'; container.appendChild(iconContainer); container.appendChild(displayNameDiv); return container; } // 创建目标物品元素 static createTargetItemDisplayElement(targetItem) { const { container, itemContainer, rightDiv } = MWI_Toolkit_Calculator.createBaseItemDisplayItem(targetItem); const needCalcCheckbox = document.createElement('input'); needCalcCheckbox.type = 'checkbox'; container.prepend(needCalcCheckbox); needCalcCheckbox.addEventListener('change', () => { targetItem.needCalc = needCalcCheckbox.checked; MWI_Toolkit_Calculator.saveAndScheduleRender(); }); // 拥有数量 const ownedSpan = document.createElement('span'); ownedSpan.style.padding = '4px 1px'; ownedSpan.style.marginLeft = '4px'; // 斜杠分隔符 const slash = document.createElement('span'); slash.textContent = "/"; slash.style.padding = '4px 1px'; // 可编辑的需求数量输入框 const targetInput = document.createElement('input'); targetInput.type = 'text'; targetInput.placeholder = '需求'; targetInput.style.background = '#dde2f8'; targetInput.style.color = '#000000'; targetInput.style.border = 'none'; targetInput.style.borderRadius = '4px'; targetInput.style.padding = '4px'; targetInput.style.margin = '2px'; targetInput.style.width = '60px'; // 绑定输入事件 targetInput.addEventListener('input', function () { // 清理非数字字符 this.value = this.value.replace(/(?!^)-|[^\d-]/g, ''); const newCount = parseInt(this.value) || 0; MWI_Toolkit_Calculator.updateTargetItem(targetItem.itemHrid, newCount); }); // 输入框获得焦点时全选内容 targetInput.addEventListener('focus', function () { setTimeout(() => { targetInput.select(); }, 0); }); // 删除按钮 const removeButton = document.createElement('button'); removeButton.style.background = '#f44336'; removeButton.style.border = 'none'; removeButton.style.borderRadius = '4px'; removeButton.style.padding = '4px'; removeButton.style.margin = '2px'; removeButton.style.cursor = 'pointer'; const removeSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); removeSvg.setAttribute('width', '18px'); removeSvg.setAttribute('height', '18px'); removeSvg.style.display = 'block'; const removeUse = document.createElementNS('http://www.w3.org/2000/svg', 'use'); removeUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', MWI_Toolkit_Utils.getIconHrefByMiscHrid('remove')); removeSvg.appendChild(removeUse); removeButton.appendChild(removeSvg); removeButton.addEventListener('click', () => { MWI_Toolkit_Calculator.removeTargetItem(targetItem.itemHrid); }); rightDiv.appendChild(ownedSpan); rightDiv.appendChild(slash); rightDiv.appendChild(targetInput); rightDiv.appendChild(removeButton); targetItem.displayElement = container; targetItem.needCalcCheckbox = needCalcCheckbox; targetItem.ownedSpan = ownedSpan; targetItem.targetInput = targetInput; } // 创建缺口物品元素 static createShortageItemDisplayElement(requiredItem) { const { container, itemContainer, rightDiv } = MWI_Toolkit_Calculator.createBaseItemDisplayItem(requiredItem); const shortageSpan = document.createElement('span'); shortageSpan.style.background = '#393a5b'; shortageSpan.style.borderRadius = '4px'; shortageSpan.style.padding = '2px 6px'; shortageSpan.style.marginLeft = '4px'; shortageSpan.style.cursor = 'pointer'; shortageSpan.addEventListener('click', (e) => { if (e.ctrlKey) { MWI_Toolkit_Calculator.TryGotoActionDetailByRequiredItem(requiredItem); } }); rightDiv.appendChild(shortageSpan); requiredItem.shortageDisplayElement = container; requiredItem.shortageSpan = shortageSpan; } // 创建需求物品元素 static createRequiredItemDisplayElement(requiredItem) { const { container, itemContainer, rightDiv } = MWI_Toolkit_Calculator.createBaseItemDisplayItem(requiredItem); const RequiredCountDiv = document.createElement('div'); RequiredCountDiv.style.padding = '4px 1px'; RequiredCountDiv.style.marginLeft = '4px'; const ownedSpan = document.createElement('span'); const equivalentSpan = document.createElement('span'); const requiredSpan = document.createElement('span'); RequiredCountDiv.appendChild(equivalentSpan); RequiredCountDiv.appendChild(ownedSpan); RequiredCountDiv.appendChild(requiredSpan); rightDiv.appendChild(RequiredCountDiv); requiredItem.requiredDisplayElement = container; requiredItem.equivalentSpan = equivalentSpan; requiredItem.ownedSpan = ownedSpan; requiredItem.requiredSpan = requiredSpan; requiredItem.requiredDiv = RequiredCountDiv; } static createBaseItemDisplayItem(displayItem) { const container = document.createElement('div'); container.className = 'Toolkit_Calculator_Container'; container.style.border = 'none'; container.style.borderRadius = '4px'; container.style.padding = '1px 4px'; container.style.margin = '2px'; container.style.display = 'flex'; container.style.alignItems = 'center'; const itemContainer = MWI_Toolkit_Calculator.createItemContainer(displayItem); container.appendChild(itemContainer); const leftDiv = document.createElement('div'); leftDiv.style.flex = '1'; container.appendChild(leftDiv); // 右侧内容 const rightDiv = document.createElement('div'); rightDiv.style.display = 'flex'; container.appendChild(rightDiv); return { container, itemContainer, rightDiv }; } // 尝试打开动作面板 static TryGotoActionDetailByRequiredItem(requiredItem) { const actionHrid = MWI_Toolkit_ActionDetailPlus.getActionHrid(requiredItem.displayName) ?? MWI_Toolkit_ActionDetailPlus.processableActionList[requiredItem.itemHrid] ?? null; if (!actionHrid) { return; } const { upgradeItemHrid, inputItems, outputItems } = MWI_Toolkit_ActionDetailPlus.calculateActionDetail(actionHrid, true); const outputCount = outputItems.find(oi => oi.itemHrid === requiredItem.itemHrid)?.count || 1; if (outputCount === 1 || outputCount === 15) { const actionCount = Math.ceil(requiredItem.getShortageCount() / outputCount); MWI_Toolkit.gameObject.handleGoToAction(actionHrid, actionCount); } else { const actionCount = MWI_Toolkit_Calculator.getRequiredTrials99(outputCount, requiredItem.getShortageCount()); MWI_Toolkit.gameObject.handleGoToAction(actionHrid, actionCount); } } static getRequiredTrials99(mu, target) { // 用拟合公式简化标准差 // sigma ≈ 0.2912 * mu^1.032 const sigma = 0.2912 * Math.pow(mu, 1.032); const z = 2.326; // 99%置信度 // 解一元二次方程 n*mu - z*sigma*sqrt(n) - target = 0 // x = sqrt(n) = (z*sigma + sqrt(z^2*sigma^2 + 4*mu*target)) / (2*mu) const x = (z * sigma + Math.sqrt(z * z * sigma * sigma + 4 * mu * target)) / (2 * mu); return Math.ceil(x * x); } } MWI_Toolkit_Calculator.targetItemCategoryMap = new Map(); MWI_Toolkit_Calculator.targetItemsMap = new Map(); MWI_Toolkit_Calculator.requiredItemsMap = new Map(); MWI_Toolkit_Calculator.tabButton = null; MWI_Toolkit_Calculator.tabPanel = null; MWI_Toolkit_Calculator.targetItemDetailsMap = new Map(); MWI_Toolkit_Calculator.shortageItemDetailsMap = new Map(); MWI_Toolkit_Calculator.requiredItemDetailsMap = new Map(); MWI_Toolkit_Calculator.itemCategoryList = [ '/item_categories/house_rooms', '/item_categories/currency', '/item_categories/loot', '/item_categories/key', '/item_categories/food', '/item_categories/drink', '/item_categories/ability_book', '/item_categories/equipment', '/item_categories/materials', '/item_categories/resource', ]; MWI_Toolkit_Calculator.renderTimeout = null; //#endregion //#region ActionDetailPlus class ItemCountComponent { } class MWI_Toolkit_ActionDetailPlus { /** * 监听页面变化 */ static initialize() { let lastPanel = null; const observer = new MutationObserver(() => { const panel = document.querySelector('[class^="SkillActionDetail_regularComponent"]'); if (panel && panel !== lastPanel) { lastPanel = panel; setTimeout(() => { MWI_Toolkit_ActionDetailPlus.enhanceSkillActionDetail(); }, 50); } }); observer.observe(document.body, { childList: true, subtree: true }); } /** * 增强技能行动详情面板 */ static enhanceSkillActionDetail() { const actionName = MWI_Toolkit_ActionDetailPlus.getActionName(); const actionHrid = MWI_Toolkit_ActionDetailPlus.getActionHrid(actionName); const { upgradeItemHrid, inputItems, outputItems } = MWI_Toolkit_ActionDetailPlus.calculateActionDetail(actionHrid); const upgradeItemComponent = { itemHrid: '', count: 0 }; if (upgradeItemHrid) { const shortageCountContainer = document.querySelector('[class^="SkillActionDetail_upgradeItemSelectorInput"]')?.parentElement?.previousElementSibling; if (shortageCountContainer) { const newTextSpan = document.createElement('span'); newTextSpan.textContent = shortageCountContainer.textContent; newTextSpan.style.height = window.getComputedStyle(document.querySelector('[class*="SkillActionDetail_levelRequirement"]')).height; shortageCountContainer.innerHTML = ''; shortageCountContainer.appendChild(newTextSpan); const shortageCountComponent = document.createElement('div'); shortageCountComponent.style.display = 'flex'; shortageCountComponent.style.alignItems = 'flex-end'; shortageCountComponent.style.flexDirection = 'column'; const shortageCountSpan = document.createElement('span'); shortageCountSpan.style.display = 'flex'; shortageCountSpan.style.alignItems = 'center'; shortageCountSpan.style.color = '#faa21e'; shortageCountComponent.appendChild(shortageCountSpan); upgradeItemComponent.itemHrid = upgradeItemHrid; upgradeItemComponent.shortageCountSpan = shortageCountSpan; upgradeItemComponent.count = 1; // 升级物品固定需求1个 shortageCountContainer.appendChild(shortageCountComponent); } } // [{itemHrid, shortageCountSpan, inventoryCountSpan, inputCountSpan, count}] const inputItemComponents = Array(); if (inputItems) { const inputItemComponentContainer = document.querySelector('[class^="SkillActionDetail_itemRequirements"]'); const shortageCountContainer = inputItemComponentContainer?.parentElement?.previousElementSibling; if (shortageCountContainer) { const newTextSpan = document.createElement('span'); newTextSpan.textContent = shortageCountContainer.textContent; newTextSpan.style.height = window.getComputedStyle(document.querySelector('[class*="SkillActionDetail_levelRequirement"]')).height; shortageCountContainer.innerHTML = ''; shortageCountContainer.appendChild(newTextSpan); const shortageCountComponent = document.createElement('div'); shortageCountComponent.style.display = 'flex'; shortageCountComponent.style.alignItems = 'flex-end'; shortageCountComponent.style.flexDirection = 'column'; const inventoryCountSpans = inputItemComponentContainer?.querySelectorAll('[class*="SkillActionDetail_inventoryCount"]'); const inputCountSpans = inputItemComponentContainer?.querySelectorAll('[class*="SkillActionDetail_inputCount"]'); const itemContainers = inputItemComponentContainer?.querySelectorAll('[class*="Item_itemContainer"]'); for (let i = 0; i < itemContainers.length; i++) { inputCountSpans[i].style.color = '#E7E7E7'; const inputItemHrid = '/items/' + itemContainers[i].querySelector('svg use').getAttribute('href').split('#').pop(); const inputItemCount = inputItems.find(item => item.itemHrid === inputItemHrid)?.count || 0; const shortageCountSpan = document.createElement('span'); shortageCountSpan.style.height = window.getComputedStyle(itemContainers[i]).height; shortageCountSpan.style.display = 'flex'; shortageCountSpan.style.alignItems = 'center'; shortageCountSpan.style.color = '#faa21e'; shortageCountComponent.appendChild(shortageCountSpan); inputItemComponents.push({ itemHrid: inputItemHrid, shortageCountSpan: shortageCountSpan, inventoryCountSpan: inventoryCountSpans[i], inputCountSpan: inputCountSpans[i], count: inputItemCount }); } shortageCountContainer.appendChild(shortageCountComponent); } } // [{itemHrid, input, count}] const outputItemComponents = Array(); let lastOutputItemComponent = document.querySelector('[class^="SkillActionDetail_maxActionCountInput"]'); const outputItemComponentContainer = lastOutputItemComponent.parentElement; const skillActionTimeInput = lastOutputItemComponent.querySelector('input'); const skillActionTimeButtons = lastOutputItemComponent.querySelectorAll('button'); for (const outputItem of outputItems) { if (outputItem.count === 1 && outputItems.length === 1) break; // 仅有一个产出且数量为1时不创建额外输入框 const { component, input } = MWI_Toolkit_ActionDetailPlus.createOutputItemComponent(outputItem.itemHrid); if (component && input) { outputItemComponentContainer.insertBefore(component, lastOutputItemComponent.nextSibling); outputItemComponents.push({ itemHrid: outputItem.itemHrid, outputItemInput: input, count: outputItem.count }); lastOutputItemComponent = component; } } // 联动 let linking = false; function updateSkillActionDetail(e) { if (linking) return; linking = true; const target = e.target; const index = outputItemComponents.findIndex(component => component.outputItemInput === target); const targetValue = parseInt(target.value, 10); if (index !== -1) { skillActionTimeInput.value = (isNaN(targetValue)) ? '∞' : Math.ceil(targetValue / outputItemComponents[index].count).toString(); MWI_Toolkit_Utils.reactInputTriggerHack(skillActionTimeInput); } const skillActionTimes = parseInt(skillActionTimeInput.value, 10); outputItemComponents.forEach(component => { if (component.outputItemInput !== target) { component.outputItemInput.value = (isNaN(skillActionTimes)) ? '∞' : Math.ceil(skillActionTimes * component.count).toString(); } }); inputItemComponents.forEach(component => { const inventoryCount = MWI_Toolkit_ItemsMap.getCount(component.itemHrid); const requiredCount = component.count * skillActionTimes; if (isNaN(skillActionTimes)) { component.shortageCountSpan.textContent = ''; component.inventoryCountSpan.style.color = ''; } else { if (requiredCount > inventoryCount) { component.shortageCountSpan.textContent = MWI_Toolkit_Utils.formatNumber(requiredCount - inventoryCount); component.inventoryCountSpan.style.color = '#f44336'; } else { component.shortageCountSpan.textContent = ' '; component.inventoryCountSpan.style.color = '#E7E7E7'; } } component.inputCountSpan.textContent = '\u00A0/ ' + MWI_Toolkit_Utils.formatNumber(component.count * ((isNaN(skillActionTimes) ? 1 : skillActionTimes))) + '\u00A0'; }); if (upgradeItemComponent.shortageCountSpan) { if (isNaN(skillActionTimes)) { upgradeItemComponent.shortageCountSpan.textContent = ''; } else { const requiredCount = upgradeItemComponent.count * skillActionTimes; const inventoryCount = MWI_Toolkit_ItemsMap.getCount(upgradeItemComponent.itemHrid); if (requiredCount > inventoryCount) { upgradeItemComponent.shortageCountSpan.textContent = MWI_Toolkit_Utils.formatNumber(requiredCount - inventoryCount); } else { upgradeItemComponent.shortageCountSpan.textContent = ' '; } } } linking = false; } skillActionTimeInput.addEventListener('input', updateSkillActionDetail); outputItemComponents.forEach(component => { component.outputItemInput.addEventListener('input', updateSkillActionDetail); }); skillActionTimeButtons.forEach(btn => { btn.addEventListener('click', () => { setTimeout(() => { skillActionTimeInput.dispatchEvent(new Event('input', { bubbles: false })); }, 20); }); }); // 初次填充 setTimeout(() => { skillActionTimeInput.dispatchEvent(new Event('input', { bubbles: false })); }, 20); } static calculateActionDetail(actionHrid, ignoreProcessingTea = false) { const actionDetail = MWI_Toolkit_ActionDetailPlus.getActionDetail(actionHrid); const actionType = actionDetail?.type?.split('/').pop() || ''; // 仅支持八种常规类型 if (!actionDetail || !actionType || !['milking', 'foraging', 'woodcutting', 'cheesesmithing', 'crafting', 'tailoring', 'cooking', 'brewing'].includes(actionType)) { console.warn('[MWI_Toolkit] 无法获取动作详情' + MWI_Toolkit_ActionDetailPlus.getActionName()); return { upgradeItemHrid: null, inputItems: null, outputItems: null }; } // console.log('MWI_Toolkit_ActionDetailPlus: 获取到动作详情', actionDetail); const upgradeItemHrid = actionDetail.upgradeItemHrid; const inputItems = actionDetail.inputItems ? JSON.parse(JSON.stringify(actionDetail.inputItems)) : null; const outputItems = actionDetail.outputItems ? JSON.parse(JSON.stringify(actionDetail.outputItems)) : Array(); const drinkSlots = MWI_Toolkit_ActionDetailPlus.getActionTypeDrinkSlots(actionType); const drinkConcentration = MWI_Toolkit_ActionDetailPlus.getDrinkConcentration(); // console.log('MWI_Toolkit_ActionDetailPlus: 获取到茶列表', drinkSlots, drinkConcentration); // 检查采集数量加成 const gatheringBuff = (drinkSlots?.some(itemHrid => itemHrid === '/items/gathering_tea') ? 0.15 * drinkConcentration : 0) + MWI_Toolkit_ActionDetailPlus.getEquipmentGatheringBuff() + MWI_Toolkit_ActionDetailPlus.getCommunityGatheringBuff(); // 检查加工茶加成 const processingBuff = (drinkSlots?.some(itemHrid => itemHrid === '/items/processing_tea') ? 0.15 * drinkConcentration : 0); // 检查美食茶加成 const gourmetBuff = (drinkSlots?.some(itemHrid => itemHrid === '/items/gourmet_tea') ? 0.12 * drinkConcentration : 0); // 检查工匠茶加成 const artisanBuff = (drinkSlots?.some(itemHrid => itemHrid === '/items/artisan_tea') ? 0.1 * drinkConcentration : 0); if (['milking', 'foraging', 'woodcutting', /*'cheesesmithing', 'crafting', 'tailoring', 'cooking', 'brewing'*/].includes(actionType)) { const dropTable = actionDetail.dropTable; for (const dropItem of dropTable) { const averageCount = dropItem.dropRate * (dropItem.minCount + dropItem.maxCount) / 2 * (1 + gatheringBuff); const processedItemHrid = MWI_Toolkit_ActionDetailPlus.processableItemList[dropItem.itemHrid]; if (processedItemHrid && !ignoreProcessingTea) { outputItems.push({ itemHrid: dropItem.itemHrid, count: averageCount * (1 - processingBuff), }); outputItems.push({ itemHrid: processedItemHrid, count: averageCount * (1 - processingBuff) / 2 / (1 - artisanBuff) + averageCount * processingBuff / 2, }); } else { outputItems.push({ itemHrid: dropItem.itemHrid, count: averageCount, }); } } } if ([/*'milking', 'foraging', 'woodcutting', 'cheesesmithing', 'crafting', 'tailoring',*/ 'cooking', 'brewing'].includes(actionType)) { for (const outputItem of outputItems) { outputItem.count = outputItem.count * (1 + gourmetBuff); } } if ([/*'milking', 'foraging', 'woodcutting',*/ 'cheesesmithing', 'crafting', 'tailoring', 'cooking', 'brewing'].includes(actionType)) { for (const inputItem of inputItems) { inputItem.count = inputItem.count * (1 - artisanBuff); } } return { upgradeItemHrid, inputItems, outputItems }; } /** * 创建output数量栏,返回 [{component, input}] * @param itemHrid 物品的唯一标识符 * @returns { component: HTMLDivElement; input: HTMLInputElement } | null */ static createOutputItemComponent(itemHrid) { const origComponent = document.querySelector('[class^="SkillActionDetail_maxActionCountInput"]'); if (!origComponent) return null; // 克隆外层div(不带子内容) const component = origComponent.cloneNode(false); const originalActionLabel = document.querySelector('[class^="SkillActionDetail_actionContainer"] [class^="SkillActionDetail_label"]'); if (Object.values(MWI_Toolkit_ActionDetailPlus.processableItemList).includes(itemHrid)) { const tab = originalActionLabel.cloneNode(false); tab.style.width = window.getComputedStyle(originalActionLabel).width; tab.className = 'SkillActionDetail_tab'; tab.textContent = '┗'; component.appendChild(tab); } // 物品图标 const itemIcon = document.createElement('div'); itemIcon.style.width = window.getComputedStyle(originalActionLabel).width; itemIcon.style.height = window.getComputedStyle(originalActionLabel).height; itemIcon.style.marginRight = '2px'; itemIcon.style.display = 'flex'; itemIcon.style.alignItems = 'center'; itemIcon.style.justifyContent = 'center'; const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '20px'); svg.setAttribute('height', '20px'); svg.style.display = 'block'; const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', MWI_Toolkit_Utils.getIconHrefByItemHrid(itemHrid)); svg.appendChild(use); itemIcon.appendChild(svg); component.appendChild(itemIcon); // 输入框 const origInputWrap = origComponent.querySelector('[class^="SkillActionDetail_input"]'); const inputWrap = origInputWrap.cloneNode(true); const origInput = origInputWrap.querySelector('input'); const input = inputWrap.querySelector('input'); input.addEventListener('focus', () => { setTimeout(() => { input.select(); }, 0); }); input.addEventListener('keydown', (event) => { if (event.key === 'Enter') { if (origInput) { origInput.dispatchEvent(event); } } }); component.appendChild(inputWrap); if (!Object.values(MWI_Toolkit_ActionDetailPlus.processableItemList).includes(itemHrid)) { // 快捷填充按钮 const btns = [ { val: 1000, txt: '1k' }, { val: 2000, txt: '2k' }, { val: 5000, txt: '5k' } ]; const origButtons = origComponent.querySelectorAll('button'); const buttonClass = origButtons.length > 0 ? origButtons[0].className : ''; btns.forEach(({ val, txt }) => { const btn = document.createElement('button'); btn.className = buttonClass; btn.textContent = txt; btn.addEventListener('click', () => { input.value = val.toString(); input.dispatchEvent(new Event('input', { bubbles: true })); }); component.appendChild(btn); }); } return { component, input }; } static getActionName() { const actionNameDiv = document.querySelector('[class^="SkillActionDetail_name"]'); return actionNameDiv ? actionNameDiv.textContent : ''; } static getActionHrid(actionName) { return MWI_Toolkit_I18n.getHridByName(actionName, 'actionNames'); } static getActionDetail(actionHrid) { return MWI_Toolkit.initClientData?.actionDetailMap?.[`${actionHrid}`]; } static getActionTypeDrinkSlots(actionType) { if (!actionType) { return []; } const drinkSlots = []; MWI_Toolkit.gameObject?.state?.actionTypeDrinkSlotsDict?.[`/action_types/${actionType}`].forEach(drink => { if (drink && drink.itemHrid) { drinkSlots.push(drink.itemHrid); } }); // 对三采添加对应的工匠茶数据用于计算加工数量 const processActionType = { milking: 'cheesesmithing', foraging: 'tailoring', woodcutting: 'crafting' }[actionType] || null; if (processActionType) { const processDrinkSlots = MWI_Toolkit.gameObject?.state?.actionTypeDrinkSlotsDict?.[`/action_types/${processActionType}`]; processDrinkSlots.forEach(drink => { if (drink && drink.itemHrid == '/items/artisan_tea') { drinkSlots.push(drink.itemHrid); } }); } return drinkSlots; } static getDrinkConcentration() { const enhancementLevel = MWI_Toolkit_ItemsMap.getMaxEnhancementLevel("/items/guzzling_pouch"); if (enhancementLevel != -1) { return 1 + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/guzzling_pouch`].equipmentDetail.noncombatStats.drinkConcentration + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/guzzling_pouch`].equipmentDetail.noncombatEnhancementBonuses.drinkConcentration * MWI_Toolkit.initClientData?.enhancementLevelTotalBonusMultiplierTable[enhancementLevel]; } return 1; } static getEquipmentGatheringBuff() { let equipmentGatheringBuff = 0; const philosophers_earrings_enhancementLevel = MWI_Toolkit_ItemsMap.getMaxEnhancementLevel("/items/philosophers_earrings"); const earrings_of_gathering_enhancementLevel = MWI_Toolkit_ItemsMap.getMaxEnhancementLevel("/items/earrings_of_gathering"); const philosophers_ring_enhancementLevel = MWI_Toolkit_ItemsMap.getMaxEnhancementLevel("/items/philosophers_ring"); const ring_of_gathering_enhancementLevel = MWI_Toolkit_ItemsMap.getMaxEnhancementLevel("/items/ring_of_gathering"); if (philosophers_earrings_enhancementLevel != -1) { equipmentGatheringBuff = equipmentGatheringBuff + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/philosophers_earrings`].equipmentDetail.noncombatStats.gatheringQuantity + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/philosophers_earrings`].equipmentDetail.noncombatEnhancementBonuses.gatheringQuantity * MWI_Toolkit.initClientData?.enhancementLevelTotalBonusMultiplierTable[philosophers_earrings_enhancementLevel]; } else if (earrings_of_gathering_enhancementLevel != -1) { equipmentGatheringBuff = equipmentGatheringBuff + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/earrings_of_gathering`].equipmentDetail.noncombatStats.gatheringQuantity + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/earrings_of_gathering`].equipmentDetail.noncombatEnhancementBonuses.gatheringQuantity * MWI_Toolkit.initClientData?.enhancementLevelTotalBonusMultiplierTable[earrings_of_gathering_enhancementLevel]; } if (philosophers_ring_enhancementLevel != -1) { equipmentGatheringBuff = equipmentGatheringBuff + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/philosophers_ring`].equipmentDetail.noncombatStats.gatheringQuantity + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/philosophers_ring`].equipmentDetail.noncombatEnhancementBonuses.gatheringQuantity * MWI_Toolkit.initClientData?.enhancementLevelTotalBonusMultiplierTable[philosophers_ring_enhancementLevel]; } else if (ring_of_gathering_enhancementLevel != -1) { equipmentGatheringBuff = equipmentGatheringBuff + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/ring_of_gathering`].equipmentDetail.noncombatStats.gatheringQuantity + MWI_Toolkit.initClientData?.itemDetailMap?.[`/items/ring_of_gathering`].equipmentDetail.noncombatEnhancementBonuses.gatheringQuantity * MWI_Toolkit.initClientData?.enhancementLevelTotalBonusMultiplierTable[ring_of_gathering_enhancementLevel]; } return equipmentGatheringBuff; } static getCommunityGatheringBuff() { const communityBuffs = MWI_Toolkit.gameObject?.state?.communityBuffs || []; for (const buff of communityBuffs) { if (buff.hrid === "/community_buff_types/gathering_quantity" && !buff.isDone) { return buff.level * 0.005 + 0.195; } } return 0; } } /** * 可加工的动作映射 */ MWI_Toolkit_ActionDetailPlus.processableActionList = { "/items/milk": "/actions/milking/cow", "/items/verdant_milk": "/actions/milking/verdant_cow", "/items/azure_milk": "/actions/milking/azure_cow", "/items/burble_milk": "/actions/milking/burble_cow", "/items/crimson_milk": "/actions/milking/crimson_cow", "/items/rainbow_milk": "/actions/milking/unicow", "/items/holy_milk": "/actions/milking/holy_cow", "/items/log": "/actions/woodcutting/tree", "/items/birch_log": "/actions/woodcutting/birch_tree", "/items/cedar_log": "/actions/woodcutting/cedar_tree", "/items/purpleheart_log": "/actions/woodcutting/purpleheart_tree", "/items/ginkgo_log": "/actions/woodcutting/ginkgo_tree", "/items/redwood_log": "/actions/woodcutting/redwood_tree", "/items/arcane_log": "/actions/woodcutting/arcane_tree", "/items/cotton": "/actions/foraging/cotton", "/items/flax": "/actions/foraging/flax", "/items/bamboo_branch": "/actions/foraging/bamboo_branch", "/items/cocoon": "/actions/foraging/cocoon", "/items/radiant_fiber": "/actions/foraging/radiant_fiber" }; /** * 可加工的物品映射 */ MWI_Toolkit_ActionDetailPlus.processableItemList = { "/items/milk": "/items/cheese", "/items/verdant_milk": "/items/verdant_cheese", "/items/azure_milk": "/items/azure_cheese", "/items/burble_milk": "/items/burble_cheese", "/items/crimson_milk": "/items/crimson_cheese", "/items/rainbow_milk": "/items/rainbow_cheese", "/items/holy_milk": "/items/holy_cheese", "/items/log": "/items/lumber", "/items/birch_log": "/items/birch_lumber", "/items/cedar_log": "/items/cedar_lumber", "/items/purpleheart_log": "/items/purpleheart_lumber", "/items/ginkgo_log": "/items/ginkgo_lumber", "/items/redwood_log": "/items/redwood_lumber", "/items/arcane_log": "/items/arcane_lumber", "/items/cotton": "/items/cotton_fabric", "/items/flax": "/items/linen_fabric", "/items/bamboo_branch": "/items/bamboo_fabric", "/items/cocoon": "/items/silk_fabric", "/items/radiant_fiber": "/items/radiant_fabric" }; //#endregion //#region Utils /** * 静态工具类 */ class MWI_Toolkit_Utils { /** * 格式化数字为字符串 * @param num 要格式化的数字 * @returns 格式化后的字符串 */ static formatNumber(num) { // 类型和有效性检查 if (!Number.isFinite(num)) { return '0'; } // 确保非负数 const normalizedNum = Math.max(0, num); // 小于1000:保留1位小数,但如果小数为0则只显示整数 if (normalizedNum <= 999) { const fixed = normalizedNum.toFixed(1); return fixed.endsWith('.0') ? Math.round(normalizedNum).toString() : fixed; } // 小于100,000:向上取整 if (normalizedNum <= 99999) { return Math.ceil(normalizedNum).toString(); } // 小于10,000,000:显示xxxK (100K~9999K) if (normalizedNum <= 9999999) { return `${Math.floor(normalizedNum / 1000)}K`; } // 小于10,000,000,000:显示xxxM (100M~9999M) if (normalizedNum <= 9999999999) { return `${Math.floor(normalizedNum / 1000000)}M`; } // 小于10,000,000,000,000:显示xxxB (100B~9999B) if (normalizedNum <= 9999999999999) { return `${Math.floor(normalizedNum / 1000000000)}B`; } // 更大的数值显示NaN return 'NaN'; } /** * 获取物品排序索引 * @param hrid 物品的唯一标识符 * @returns 物品的排序索引 */ static getSortIndexByItemHrid(hrid) { return (MWI_Toolkit.initClientData?.itemDetailMap?.[hrid]?.sortIndex || 9999); } /** * 获取物品排序索引 * @param hrid 物品的唯一标识符 * @returns 物品的排序索引 */ static getSortIndexByHouseRoomHrid(hrid) { return (MWI_Toolkit.initClientData?.houseRoomDetailMap?.[hrid]?.sortIndex || 0) - 9999; } /** * 获取物品图标的链接 * @param itemHrid 物品的唯一标识符 * @returns 物品图标的链接 */ static getIconHrefByItemHrid(itemHrid) { return '/static/media/items_sprite.d4d08849.svg#' + (itemHrid.split('/').pop() || ''); } /** * 获取技能图标的链接 * @param skillHrid 技能的唯一标识符 * @returns 技能图标的链接 */ static getIconHrefBySkillHrid(skillHrid) { return '/static/media/skills_sprite.3bb4d936.svg#' + (skillHrid.split('/').pop() || ''); } /** * 获取房屋图标的链接 * @param houseRoomHrid 房屋的唯一标识符 * @returns 房屋图标的链接 */ static getIconHrefByHouseRoomHrid(houseRoomHrid) { return MWI_Toolkit_Utils.getIconHrefBySkillHrid(MWI_Toolkit.initClientData?.houseRoomDetailMap?.[houseRoomHrid]?.skillHrid || houseRoomHrid); } /** * 获取杂项图标的链接 * @param hrid 杂项的唯一标识符 * @returns 杂项图标的链接 */ static getIconHrefByMiscHrid(hrid) { return '/static/media/misc_sprite.6fa5e97c.svg#' + (hrid.split('/').pop() || ''); } /** * 触发React输入框的change事件 * 通过操作React内部的_valueTracker来触发React的事件系统 * @param inputElem HTML输入元素 */ static reactInputTriggerHack(inputElem) { const lastValue = inputElem.value; const event = new Event("input", { bubbles: true }); // 添加自定义标记 event.simulated = true; // 访问React内部的value tracker const tracker = inputElem._valueTracker; if (tracker) { // 触发变更:设置为不同的值以触发React的change检测 tracker.setValue(lastValue === '' ? ' ' : ''); } inputElem.dispatchEvent(event); } } //#endregion //#region I18n /** * 国际化静态工具类实现 * 提供游戏内物品和其他资源的多语言名称查询功能 */ class MWI_Toolkit_I18n { /** * 获取物品的本地化名称 * @param itemHrid 物品的唯一标识符 * @returns 本地化后的物品名称,如果找不到则返回原始HRID */ static getItemName(itemHrid) { return MWI_Toolkit_I18n.getName(itemHrid, "itemNames"); } /** * 获取资源的本地化名称(通用方法) * @param hrid 资源的唯一标识符 * @param category 资源分类(houseRoomNames, actionNames, itemNames) * @returns 本地化后的名称,如果找不到则返回原始HRID */ static getName(hrid, category) { if (!hrid || !category) { return hrid; } // 特例自定义itemCategory名称 if (hrid === '/item_categories/house_rooms') { return MWI_Toolkit?.getGameLanguage() === 'zh' ? '房屋' : 'House'; } if (hrid === '/item_categories/materials') { return MWI_Toolkit?.getGameLanguage() === 'zh' ? '材料' : 'Materials'; } return MWI_Toolkit.gameObject?.props?.i18n?.options?.resources?.[MWI_Toolkit?.getGameLanguage()]?.translation?.[category]?.[hrid] || hrid; } /** * 根据物品名称反查HRID * @param itemName 物品的本地化名称 * @returns 对应的物品HRID,如果找不到则返回null */ static getItemHridByName(itemName) { return MWI_Toolkit_I18n.getHridByName(itemName, "itemNames"); } /** * 根据名称反查HRID(通用方法) * @param name 资源的本地化名称 * @param category 资源分类(houseRoomNames, actionNames, itemNames) * @returns 对应的HRID,如果找不到则返回null */ static getHridByName(name, category) { if (!name || !category) { return null; } return Object.entries(MWI_Toolkit.gameObject?.props?.i18n?.options?.resources?.[MWI_Toolkit?.getGameLanguage()]?.translation?.[category] || {}) .find(([, v]) => (v || '').toLowerCase() === name.toLowerCase().trim())?.[0] ?? null; } } //#endregion //#region ItemsMap /** * 物品映射管理类实现 * 使用二级Map结构存储物品数据:itemHrid -> enhancementLevel -> count * 提供物品查询和事件监听功能 */ class MWI_Toolkit_ItemsMap { /** * 查询指定物品和强化等级的数量 * @param itemHrid 物品的唯一标识符 * @param enhancementLevel 强化等级,默认为0 * @returns 物品数量,如果不存在则返回0 */ static getCount(itemHrid, enhancementLevel = 0) { return MWI_Toolkit_ItemsMap.map.get(itemHrid)?.get(enhancementLevel) ?? 0; } /** * 查询指定物品的最大强化等级 * @param itemHrid 物品的唯一标识符 * @returns 最大强化等级,如果物品不存在或所有数量为0则返回-1 */ static getMaxEnhancementLevel(itemHrid) { const m = MWI_Toolkit_ItemsMap.map.get(itemHrid); if (!m) { return -1; } let max = -1; for (const [level, count] of m) { if (count > 0 && level > max) { max = level; } } return max; } /** * 更新物品数据 * @param endCharacterItems 要更新的物品列表 */ static update(endCharacterItems) { if (!endCharacterItems) { return; } for (const item of endCharacterItems) { if (!MWI_Toolkit_ItemsMap.map.has(item.itemHrid)) { MWI_Toolkit_ItemsMap.map.set(item.itemHrid, new Map()); } MWI_Toolkit_ItemsMap.map.get(item.itemHrid).set(item.enhancementLevel, item.count); } MWI_Toolkit_ItemsMap.itemsUpdatedCallbacks.forEach(cb => { try { cb(endCharacterItems); } catch (e) { console.error('[MWI_Toolkit] Error in item updated callback:', e); } }); } /** * 清空所有物品数据 */ static clear() { MWI_Toolkit_ItemsMap.map.clear(); } } /** 物品数据映射表:itemHrid -> (enhancementLevel -> count) */ MWI_Toolkit_ItemsMap.map = new Map(); /** 物品更新事件的回调函数列表 */ MWI_Toolkit_ItemsMap.itemsUpdatedCallbacks = []; //#endregion //#region MWI_Toolkit /** * MWI工具包主类 * 提供游戏数据抓取、国际化、物品管理等核心功能 * 使用单例模式确保全局只有一个实例 */ class MWI_Toolkit { static getGameLanguage() { return MWI_Toolkit.gameObject?.language || 'zh'; } /** * 启动工具包 * 等待游戏页面加载完成后进行初始化 */ static start() { MWI_Toolkit.setupWebSocketInterceptor(); MWI_Toolkit.waitForElement('[class^="GamePage"]', () => { MWI_Toolkit.initialize(); }); } /** * 设置 WebSocket 消息拦截器 * 通过劫持 MessageEvent.prototype.data 来拦截所有 WebSocket 消息 */ static setupWebSocketInterceptor() { const oriGet = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data")?.get; if (!oriGet) { return; } Object.defineProperty(MessageEvent.prototype, "data", { get: function () { const socket = this.currentTarget; // 只拦截游戏服务器的 WebSocket 消息 if (!(socket instanceof WebSocket) || (socket.url.indexOf("api.milkywayidle.com/ws") === -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") === -1)) { return oriGet.call(this); } const message = oriGet.call(this); Object.defineProperty(this, "data", { value: message }); // 防止循环调用 MWI_Toolkit?.handleWebSocketMessage(message); return message; } }); } /** * 处理 WebSocket 消息 * 解析消息并根据类型进行相应处理 * @param message WebSocket 消息内容(JSON字符串) */ static handleWebSocketMessage(message) { try { const obj = JSON.parse(message); // 类型守卫:检查是否为对象 if (!obj || typeof obj !== 'object') return; const msgObj = obj; // 处理角色初始化消息(使用双重断言) if (msgObj.type === "init_character_data" && Array.isArray(msgObj.characterItems)) { MWI_Toolkit.handleInitCharacterData(obj); } // 处理物品变更消息(使用双重断言) else if (Array.isArray(msgObj.endCharacterItems)) { MWI_Toolkit.handleEndCharacterItems(obj); } } catch { // 忽略解析错误(非JSON消息或其他错误) } } /** * 处理角色初始化数据 * 当切换角色或刷新页面时会收到此消息 * @param data 角色初始化数据 */ static handleInitCharacterData(data) { // 清空并初始化物品映射表 MWI_Toolkit_ItemsMap.clear(); MWI_Toolkit_ItemsMap.update(data.characterItems); if (!MWI_Toolkit.initialized) { return; } console.log("[MWI_Toolkit] 界面刷新"); MWI_Toolkit_Calculator.initializeCalculatorUI(); MWI_Toolkit.waitForElement('[class^="GamePage"]', () => { MWI_Toolkit.gameObject = MWI_Toolkit.getGameObject(); }); } /** * 处理物品变更数据 * 当物品数量、强化等级等发生变化时会收到此消息 * @param data 包含变更后物品数据的消息 */ static handleEndCharacterItems(data) { // 更新物品映射表 MWI_Toolkit_ItemsMap.update(data.endCharacterItems); } /** * 等待指定的DOM元素出现 * 使用 MutationObserver 监听DOM变化 * @param callback 元素出现后执行的回调函数 */ static waitForElement(selector, callback) { const el = document.querySelector(selector); if (el) { // 元素已存在,直接执行回调 callback(); return; } // 元素不存在,监听DOM变化 const observer = new MutationObserver(() => { const el = document.querySelector(selector); if (el) { observer.disconnect(); callback(); } }); observer.observe(document.body, { childList: true, subtree: true }); } /** * 初始化工具包 * 获取游戏对象实例并设置初始化标志 */ static initialize() { MWI_Toolkit.gameObject = MWI_Toolkit.getGameObject(); MWI_Toolkit.initClientData = MWI_Toolkit.getInitClientData(); if (!MWI_Toolkit.gameObject || !MWI_Toolkit.initClientData) { console.error("[MWI_Toolkit] 初始化失败"); return; } MWI_Toolkit.initialized = true; MWI_Toolkit_ActionDetailPlus.initialize(); MWI_Toolkit_Calculator.initialize(); console.log("[MWI_Toolkit] 已初始化"); console.log(MWI_Toolkit.gameObject, MWI_Toolkit.initClientData); } /** * 从DOM元素获取React组件实例 * 通过访问React内部的Fiber节点来获取组件实例 * @returns React组件实例,如果未找到则返回null */ static getGameObject() { // (e => e?.[Object.keys(e).find(k => k.startsWith('__reactFiber$'))]?.return?.stateNode)(document.querySelector('[class^="GamePage"]')) const gamePageElement = document.querySelector('[class^="GamePage"]'); if (!gamePageElement) return null; // 查找React Fiber的key(格式:__reactFiber$xxx) const reactKey = Object.keys(gamePageElement).find(k => k.startsWith('__reactFiber$')); if (!reactKey) return null; // 通过Fiber节点获取组件实例 const fiber = gamePageElement[reactKey]; return fiber?.return?.stateNode || null; } /** * 从localStorage获取初始化客户端数据 * 数据以压缩的UTF-16字符串形式存储 * @returns 初始化客户端数据对象,如果未找到则返回null */ static getInitClientData() { const compressedData = localStorage.getItem("initClientData"); if (compressedData) { const decompressedData = LZString.decompressFromUTF16(compressedData); return JSON.parse(decompressedData); } return null; } } /** 初始化客户端数据(从localStorage获取) */ MWI_Toolkit.initClientData = null; /** 游戏对象实例(从React组件获取) */ MWI_Toolkit.gameObject = null; /** 是否已初始化 */ MWI_Toolkit.initialized = false; //#endregion // 防止重复加载 if (window.MWI_Toolkit_Started) { return; } window.MWI_Toolkit_Started = true; // 启动工具包 MWI_Toolkit.start(); })();