// ==UserScript==
// @name MWI_Toolkit_Calculator
// @namespace http://tampermonkey.net/
// @version 2.2.7
// @description MWI计算器
// @author zqzhang1996
// @icon https://www.milkywayidle.com/favicon.svg
// @match https://www.milkywayidle.com/*
// @match https://test.milkywayidle.com/*
// @require https://cdn.jsdelivr.net/npm/[email protected]/libs/lz-string.min.js
// @require https://update.greasyfork.org/scripts/550719/1681178/MWI_Toolkit.js
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @run-at document-body
// @license MIT
// ==/UserScript==
(function () {
'use strict';
//#region 数据模型层
// 基础数据结构
class Item {
constructor(itemHrid, count) {
this.itemHrid = itemHrid;
this.count = count;
}
}
// 显示物品类 - 包含计算和显示逻辑
class DisplayItem {
constructor(itemHrid, ownedCount, requiredCount) {
this.itemHrid = itemHrid;
this.ownedCount = ownedCount;
this.requiredCount = requiredCount;
this.missingCount = Math.max(0, requiredCount - ownedCount);
// DOM 元素引用
this.domElement = null;
this.ownedCountElement = null;
this.requiredCountElement = null;
this.missingCountElement = null;
this.initDisplayProperties();
}
initDisplayProperties() {
if (this.itemHrid.includes('/items/')) {
// 显示名称和图标等属性初始化
this.displayName = Utils.getItemDisplayName(this.itemHrid);
this.iconHref = Utils.getIconHrefByItemHrid(this.itemHrid);
this.sortIndex = Utils.getSortIndexByHrid(this.itemHrid);
}
else if (this.itemHrid.includes('/house_rooms/')) {
// 显示名称和图标等属性初始化
this.displayName = Utils.getHouseRoomDisplayName(this.itemHrid);
this.iconHref = Utils.getIconHrefByHouseRoomHrid(this.itemHrid);
this.sortIndex = Utils.getSortIndexByHrid(this.itemHrid);
}
}
// 更新数据并同步到DOM
updateCounts(ownedCount, requiredCount) {
this.ownedCount = ownedCount;
this.requiredCount = requiredCount;
this.missingCount = Math.max(0, requiredCount - ownedCount);
// 仅在数值变化时才更新DOM,避免丢失选中状态
if (this.ownedCountElement) {
const newText = Utils.formatNumber(this.ownedCount);
if (this.ownedCountElement.textContent !== newText) {
this.ownedCountElement.textContent = newText;
}
}
if (this.missingCountElement) {
const newText = Utils.formatNumber(this.missingCount);
if (this.missingCountElement.textContent !== newText) {
this.missingCountElement.textContent = newText;
}
}
if (this.requiredCountElement) {
// requiredCountElement 是输入框,无需对数字进行格式化
const newValue = this.requiredCount;
if (this.requiredCountElement.value !== newValue) {
this.requiredCountElement.value = newValue;
}
}
}
// 设置DOM元素引用
setDomReferences(domElement, ownedCountElement = null, requiredCountElement = null, missingCountElement = null) {
this.domElement = domElement;
this.ownedCountElement = ownedCountElement;
this.requiredCountElement = requiredCountElement;
this.missingCountElement = missingCountElement;
}
// 销毁DOM引用
destroyDomReferences() {
this.domElement = null;
this.ownedCountElement = null;
this.requiredCountElement = null;
this.missingCountElement = null;
}
}
//#endregion
//#region 工具类
class Utils {
// 格式化数字显示
static formatNumber(num) {
if (typeof num !== 'number' || isNaN(num)) return '0';
if (num < 0) num = 0;
if (num < 1000) {
// 整数部分<=3位,保留1位小数,但如果小数为0则只显示整数
const fixed = num.toFixed(1);
if (fixed.endsWith('.0')) {
return Math.round(num).toString();
}
return fixed;
} else if (num < 100000) {
// 整数部分<=5位,向上取整
return Math.ceil(num).toString();
} else if (num < 10_000_000) {
// 10,000,000~9,999,999 显示xxxK
return Math.floor(num / 1000) + 'K';
} else if (num < 10_000_000_000) {
// 10,000,000~9,999,999,999 显示xxxM
return Math.floor(num / 1_000_000) + 'M';
} else if (num < 10_000_000_000_000) {
// 10,000,000,000~9,999,999,999,999 显示xxxB
return Math.floor(num / 1_000_000_000) + 'B';
} else {
// 更大的数值显示NaN
return 'NaN';
}
}
// 获取物品排序索引
static getSortIndexByHrid(hrid) {
if (hrid.includes('/items/')) {
return window.MWI_Toolkit?.init_client_data?.itemDetailMap?.[hrid]?.sortIndex || 9999;
}
if (hrid.includes('/house_rooms/')) {
return window.MWI_Toolkit?.init_client_data?.houseRoomDetailMap?.[hrid]?.sortIndex - 9999 || -9999;
}
return 9999;
}
static getIconHrefByItemHrid(itemHrid) {
return '/static/media/items_sprite.d4d08849.svg#' + itemHrid.split('/').pop();
}
static getIconHrefBySkillHrid(skillHrid) {
return '/static/media/skills_sprite.3bb4d936.svg#' + skillHrid.split('/').pop();
}
static getIconHrefByHouseRoomHrid(houseRoomHrid) {
const skillHrid = window.MWI_Toolkit?.init_client_data?.houseRoomDetailMap?.[houseRoomHrid]?.skillHrid || houseRoomHrid;
return Utils.getIconHrefBySkillHrid(skillHrid);
}
static getIconHrefByMiscHrid(hrid) {
return '/static/media/misc_sprite.6fa5e97c.svg#' + hrid.split('/').pop();
}
static getItemDisplayName(itemHrid) {
return window.MWI_Toolkit?.i18n?.getItemName(itemHrid, MWI_Toolkit_Calculator_App.Language);
}
static getHouseRoomDisplayName(houseRoomHrid) {
return window.MWI_Toolkit?.i18n?.getName(houseRoomHrid, "houseRoomNames", MWI_Toolkit_Calculator_App.Language);
}
}
//#endregion
//#region 核心计算引擎
class MWI_Toolkit_Calculator_Core {
constructor() {
this.targetItems = [];
}
// 递归计算所需材料
calculateRequiredItems(itemHrid, count) {
let requiredItems = [];
if (itemHrid.includes('/house_rooms/')) {
// 处理房屋房间逻辑
return this.calculateRequiredItemsForHouseRoom(itemHrid, count);
}
requiredItems.push(new Item(itemHrid, count));
const actionTypes = ["cheesesmithing", "crafting", "tailoring", "cooking", "brewing"];
const itemName = itemHrid.split('/').pop();
for (const actionType of actionTypes) {
const actionHrid = `/actions/${actionType}/${itemName}`;
if (window.MWI_Toolkit?.init_client_data?.actionDetailMap?.hasOwnProperty(actionHrid)) {
const actionDetail = window.MWI_Toolkit.init_client_data.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 === itemHrid);
if (matchingOutput) {
outputCount = matchingOutput.count;
}
}
const actionTypeDrinkSlots = this.getActionTypeDrinkSlots(actionType);
// 检查工匠茶加成
let artisanBuff = 0;
if (actionTypeDrinkSlots?.some(slot => slot && slot.itemHrid === '/items/artisan_tea')) {
artisanBuff = 0.1 * this.getDrinkConcentration();
}
// 检查美食茶加成
let gourmetBuff = 0;
if (actionTypeDrinkSlots?.some(slot => slot && slot.itemHrid === '/items/gourmet_tea')) {
gourmetBuff = 0.12 * this.getDrinkConcentration();
}
// 递归计算输入材料
for (const input of inputItems) {
const adjustedCount = input.count * count / outputCount * (1 - artisanBuff) / (1 + gourmetBuff);
requiredItems = this.mergeMaterialArrays(requiredItems, this.calculateRequiredItems(input.itemHrid, adjustedCount));
}
// 处理升级物品(不适用工匠茶加成)
if (upgradeItemHrid) {
requiredItems = this.mergeMaterialArrays(requiredItems, this.calculateRequiredItems(upgradeItemHrid, count / outputCount / (1 + gourmetBuff)));
}
return requiredItems;
}
}
return requiredItems;
}
// 计算房屋房间所需材料
calculateRequiredItemsForHouseRoom(houseRoomHrid, level) {
let targetItems = [];
const characterHouseRoomLevel = window.MWI_Toolkit?.init_character_data?.characterHouseRoomMap[houseRoomHrid]?.level || 0;
const upgradeCostsMap = window.MWI_Toolkit?.init_client_data?.houseRoomDetailMap?.[houseRoomHrid]?.upgradeCostsMap;
for (let i = characterHouseRoomLevel + 1; i <= level && i <= 8; i++) {
targetItems = targetItems.concat(upgradeCostsMap[i] || []);
}
return this.batchCalculateRequiredItems(targetItems);
}
// 批量计算材料需求
batchCalculateRequiredItems(targetItems) {
let result = [];
for (const targetItem of targetItems) {
const requiredItems = this.calculateRequiredItems(targetItem.itemHrid, targetItem.count);
result = this.mergeMaterialArrays(result, requiredItems);
}
return result;
}
// 计算等效材料
calculateEquivalentItems(requiredItems, ownedItems) {
const equivalentItems = [];
for (const ownedItem of ownedItems) {
// 这里count取requiredItems和ownedItems中的较小值并乘-1,表示用于抵消需求
const requiredItem = requiredItems.find(ri => ri.itemHrid === ownedItem.itemHrid);
if (requiredItem) {
const equivalentCount = Math.min(requiredItem.count, ownedItem.count) * -1;
equivalentItems.push(new Item(ownedItem.itemHrid, equivalentCount));
}
}
return this.batchCalculateRequiredItems(equivalentItems);
}
// 合并材料数组并按排序顺序返回
mergeMaterialArrays(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, new Item(item.itemHrid, item.count));
}
}
// 排序
return Array.from(map.values()).sort(
(a, b) => Utils.getSortIndexByHrid(a.itemHrid) - Utils.getSortIndexByHrid(b.itemHrid)
);
}
// 检查拥有的物品
checkOwnedItems(requiredItems) {
const ownedItems = [];
for (const requiredItem of requiredItems) {
const ownedCount = window.MWI_Toolkit?.characterItems?.getCount(requiredItem.itemHrid) || 0;
ownedItems.push(new Item(requiredItem.itemHrid, ownedCount));
}
return ownedItems;
}
// 获取茶列表
getActionTypeDrinkSlots(actionType) {
return window.MWI_Toolkit?.init_character_data?.actionTypeDrinkSlotsMap?.[`/action_types/${actionType}`];
}
// 获取饮料浓度系数
getDrinkConcentration() {
let drinkConcentration = 1;
if (window.MWI_Toolkit?.init_client_data && window.MWI_Toolkit?.characterItems) {
const enhancementLevel = window.MWI_Toolkit.characterItems.getMaxEnhancementLevel("/items/guzzling_pouch");
if (enhancementLevel != -1) {
drinkConcentration = 1
+ window.MWI_Toolkit.init_client_data.itemDetailMap?.[`/items/guzzling_pouch`].equipmentDetail.noncombatStats.drinkConcentration
+ window.MWI_Toolkit.init_client_data.itemDetailMap?.[`/items/guzzling_pouch`].equipmentDetail.noncombatEnhancementBonuses.drinkConcentration
* window.MWI_Toolkit.init_client_data.enhancementLevelTotalBonusMultiplierTable[enhancementLevel];
}
}
return drinkConcentration;
}
}
//#endregion
//#region 数据持久化管理
class MWI_Toolkit_Calculator_DataManager {
constructor() {
this.characterID = null;
this.storageKey = null;
}
// 初始化存储键
initStorageKey() {
try {
const characterID = window.MWI_Toolkit?.init_character_data?.character?.id;
if (characterID) {
this.characterID = characterID;
this.storageKey = `MWI_Toolkit_Calculator_targetItems_${characterID}`;
}
} catch (error) {
console.error('[MWI计算器] 初始化存储键失败:', error);
}
}
// 保存目标物品
saveTargetItems(targetItems) {
if (!this.storageKey) {
return false;
}
try {
const dataToSave = targetItems.map(item => ({
itemHrid: item.itemHrid,
count: item.count
}));
GM_setValue(this.storageKey, JSON.stringify(dataToSave));
return true;
} catch (error) {
return false;
}
}
// 加载目标物品
loadTargetItems() {
if (this.characterID) {
return this.tryLoadTargetItemsFromCharacterID(this.characterID);
}
}
// 从特定角色ID加载数据
tryLoadTargetItemsFromCharacterID(characterID) {
const storageKeys = GM_listValues();
const storageKey = storageKeys.find(key => key.includes(characterID));
if (!storageKey) return;
try {
const savedData = GM_getValue(storageKey, '[]');
const loadedItems = JSON.parse(savedData);
// 验证并转换为Item实例
const validItems = loadedItems.map(item => {
if (item && item.itemHrid && typeof item.count === 'number') {
return new Item(item.itemHrid, item.count);
}
return null;
}).filter(item => item !== null);
if (validItems.length > 0) {
MWI_Toolkit_Calculator_App.Core.targetItems = loadedItems;
MWI_Toolkit_Calculator_App.UIManager.clearAllDisplayItems();
MWI_Toolkit_Calculator_App.UIManager.renderItemsDisplay();
// 替换旧的键为新的
if (!storageKey.startsWith('MWI_Toolkit_Calculator_targetItems_')
&& storageKey.includes(this.characterID)) {
GM_deleteValue(storageKey);
}
this.saveTargetItems(MWI_Toolkit_Calculator_App.Core.targetItems);
}
} catch { }
}
// 清空保存的数据
clearSavedData() {
if (!this.storageKey) {
return false;
}
try {
GM_setValue(this.storageKey, '[]');
return true;
} catch (error) {
return false;
}
}
}
//#endregion
//#region UI 组件管理
class MWI_Toolkit_Calculator_UIManager {
constructor() {
this.tabButton = null;
this.tabPanel = null;
this.targetItemDiv = null;
this.missingItemDiv = null;
// DisplayItem 实例管理
this.targetDisplayItems = new Map(); // itemHrid -> DisplayItem
this.missingDisplayItems = new Map(); // itemHrid -> DisplayItem
}
// 初始化UI
initialize() {
// 已有标签页则不重复初始化
if (document.querySelector('[class^="Toolkit_Calculator_Container"]')) { return; }
if (document.title.includes('Milky Way Idle')) {
MWI_Toolkit_Calculator_App.Language = 'en';
}
else {
MWI_Toolkit_Calculator_App.Language = 'zh';
}
// 获取容器
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计算器] 无法找到标签页容器');
return;
}
this.createCalculatorTab(tabsContainer, tabPanelsContainer);
console.log('[MWI计算器] UI初始化完成');
}
// 创建MWI计算器标签页
createCalculatorTab(tabsContainer, tabPanelsContainer) {
// 新增"MWI计算器"按钮
const oldTabButtons = tabsContainer.querySelectorAll("button");
this.tabButton = oldTabButtons[1].cloneNode(true);
this.tabButton.children[0].textContent = (MWI_Toolkit_Calculator_App.Language === 'zh') ? 'MWI计算器' : 'MWI_Calculator';
oldTabButtons[0].parentElement.appendChild(this.tabButton);
// 新增MWI计算器tabPanel
const oldTabPanels = tabPanelsContainer.querySelectorAll('[class*="TabPanel_tabPanel"]');
this.tabPanel = oldTabPanels[1].cloneNode(false);
oldTabPanels[0].parentElement.appendChild(this.tabPanel);
this.bindCalculatorTabEvents(oldTabButtons, oldTabPanels);
// 创建计算器面板
const calculatorPanel = this.createCalculatorPanel();
this.tabPanel.appendChild(calculatorPanel);
}
// 绑定标签页事件
bindCalculatorTabEvents(oldTabButtons, oldTabPanels) {
for (let i = 0; i < oldTabButtons.length; i++) {
oldTabButtons[i].addEventListener('click', (event) => {
this.tabPanel.hidden = true; // 强制隐藏
this.tabPanel.classList.add('TabPanel_hidden__26UM3');
this.tabButton.classList.remove('Mui-selected');
this.tabButton.setAttribute('aria-selected', 'false');
this.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);
}
this.tabButton.addEventListener('click', (event) => {
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');
});
this.tabButton.classList.add('Mui-selected');
this.tabButton.setAttribute('aria-selected', 'true');
this.tabButton.tabIndex = 0;
this.tabPanel.classList.remove('TabPanel_hidden__26UM3');
this.tabPanel.hidden = false; // 显示目标
}, true);
}
// 创建计算器面板
createCalculatorPanel() {
const calculatorPanel = document.createElement('div');
calculatorPanel.className = 'Toolkit_Calculator_Container';
// 创建物品搜索区域
const addItemSection = this.createAddItemSection();
calculatorPanel.appendChild(addItemSection);
// 创建左右分栏布局
this.targetItemDiv = document.createElement('div');
this.targetItemDiv.style.display = 'inline-block';
this.targetItemDiv.style.verticalAlign = 'top';
this.targetItemDiv.style.width = '60%';
this.missingItemDiv = document.createElement('div');
this.missingItemDiv.style.display = 'inline-block';
this.missingItemDiv.style.verticalAlign = 'top';
this.missingItemDiv.style.width = '40%';
calculatorPanel.appendChild(this.targetItemDiv);
calculatorPanel.appendChild(this.missingItemDiv);
return calculatorPanel;
}
// 创建添加物品区域
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 = this.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 = this.createHouseRoomSelectionComponent();
rightSection.appendChild(houseContainer);
addItemSection.appendChild(leftSection);
addItemSection.appendChild(rightSection);
return addItemSection;
}
// 创建物品搜索组件
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_Calculator_App.Language === '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_Calculator_App.Language === 'zh') ? '数量' : 'Count';
countInput.style.imeMode = 'disabled';
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_Calculator_App.Language === '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.width = '35px';
addButton.style.cursor = 'pointer';
// 清空按钮
const clearAllButton = document.createElement('button');
clearAllButton.textContent = (MWI_Toolkit_Calculator_App.Language === '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.width = (MWI_Toolkit_Calculator_App.Language === 'zh') ? '35px' : '40px';
clearAllButton.style.cursor = 'pointer';
// 绑定搜索事件
this.bindItemSearchComponentEvents(itemSearchInput, countInput, searchResults, addButton, clearAllButton);
itemSearchComponent.appendChild(itemSearchInput);
itemSearchComponent.appendChild(countInput);
itemSearchComponent.appendChild(addButton);
itemSearchComponent.appendChild(clearAllButton);
itemSearchComponent.appendChild(searchResults);
return itemSearchComponent;
}
// 填充搜索结果
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', Utils.getIconHrefByItemHrid(itemHrid));
svg.appendChild(use);
itemIcon.appendChild(svg);
// 物品名称
const itemName = document.createElement('span');
itemName.textContent = window.MWI_Toolkit.i18n.getItemName(itemHrid, MWI_Toolkit_Calculator_App.Language) || 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);
});
}
// 绑定搜索相关事件
bindItemSearchComponentEvents(itemSearchInput, countInput, searchResults, addButton, clearAllButton) {
// 输入框获得焦点时全选内容
itemSearchInput.addEventListener('focus', function () {
setTimeout(() => {
itemSearchInput.select();
}, 0);
});
// 搜索功能
itemSearchInput.addEventListener('input', (event) => {
const searchTerm = event.target.value.toLowerCase().trim();
if (searchTerm.length < 2) {
searchResults.style.display = 'none';
return;
}
// 获取并过滤物品
const itemDetailMap = window.MWI_Toolkit?.init_client_data?.itemDetailMap;
if (!itemDetailMap) return;
const filteredItems = Object.keys(itemDetailMap)
.filter(itemHrid => {
const itemName = window.MWI_Toolkit.i18n.getItemName(itemHrid, MWI_Toolkit_Calculator_App.Language) || itemHrid;
return itemName.toLowerCase().includes(searchTerm);
})
.sort((a, b) => {
const sortIndexA = Utils.getSortIndexByHrid(a);
const sortIndexB = Utils.getSortIndexByHrid(b);
return sortIndexA - sortIndexB;
});
if (filteredItems.length === 0) {
searchResults.style.display = 'none';
return;
}
this.populateSearchResults(searchResults, filteredItems, (itemHrid) => {
itemSearchInput.value = window.MWI_Toolkit.i18n.getItemName(itemHrid, MWI_Toolkit_Calculator_App.Language) || itemHrid;
searchResults.style.display = 'none';
});
searchResults.style.display = 'block';
});
// 键盘操作
itemSearchInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.addItemAndResetItemSearchComponent(itemSearchInput, countInput, searchResults);
} else if (event.key === 'Escape') {
searchResults.style.display = 'none';
}
});
// 输入框获得焦点时全选内容
countInput.addEventListener('focus', function () {
setTimeout(() => {
countInput.select();
}, 0);
});
// 仅允许输入数字
countInput.addEventListener('input', function () {
this.value = this.value.replace(/\D/g, '');
if (this.value !== '' && parseInt(this.value) < 1) {
this.value = '1';
}
});
// 键盘操作
countInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.addItemAndResetItemSearchComponent(itemSearchInput, countInput, searchResults);
} else if (event.key === 'Escape') {
searchResults.style.display = 'none';
}
});
// 添加按钮事件
addButton.addEventListener('click', () => {
this.addItemAndResetItemSearchComponent(itemSearchInput, countInput, searchResults);
});
// 清空按钮事件
clearAllButton.addEventListener('click', () => {
if (confirm((MWI_Toolkit_Calculator_App.Language === 'zh') ? '确定要清空所有目标物品吗?' : 'Are you sure you want to clear all target items?')) {
// 通过事件处理器清空
if (MWI_Toolkit_Calculator_App.EventHandler) {
MWI_Toolkit_Calculator_App.EventHandler.clearAllTargetItems();
}
}
});
// 点击其他地方隐藏搜索结果
document.addEventListener('click', (event) => {
if (!searchResults.contains(event.target) && !itemSearchInput.contains(event.target)) {
searchResults.style.display = 'none';
}
});
}
// 添加物品并重置搜索组件(包含itemHrid获取和判空)
addItemAndResetItemSearchComponent(itemSearchInput, countInput, searchResults) {
const InputValue = itemSearchInput.value.trim();
// 如果InputValue是纯数字
if (/^\d+$/.test(InputValue)) {
MWI_Toolkit_Calculator_App.DataManager.tryLoadTargetItemsFromCharacterID(InputValue);
return;
}
const itemHrid = window.MWI_Toolkit.i18n.getItemHridByName(InputValue, MWI_Toolkit_Calculator_App.Language);
if (!itemHrid) return;
const count = parseInt(countInput.value) || 1;
if (MWI_Toolkit_Calculator_App.EventHandler) {
MWI_Toolkit_Calculator_App.EventHandler.addTargetItem(itemHrid, count);
}
itemSearchInput.value = '';
countInput.value = '1';
searchResults.style.display = 'none';
}
// 创建房屋选择区域
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 = this.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_Calculator_App.Language === 'zh') ? '等级' : 'Level';
levelInput.style.imeMode = 'disabled';
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_Calculator_App.Language === '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';
// 绑定事件
this.bindHouseRoomSelectionComponentEvents(dropdown, levelInput, addListButton);
HouseRoomSelectionComponent.appendChild(dropdown);
HouseRoomSelectionComponent.appendChild(levelInput);
HouseRoomSelectionComponent.appendChild(addListButton);
return HouseRoomSelectionComponent;
}
// 创建房屋类型下拉菜单
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 = this.createHouseRoomTypeOptions(selected, dropdown);
HouseRoomTypeOptions.forEach(optionItem => { list.appendChild(optionItem); });
HouseRoomTypeOptions[0] && HouseRoomTypeOptions[0].click(); // 默认选中第一个
dropdown.appendChild(selected);
dropdown.appendChild(list);
// 点击展开/收起
selected.addEventListener('click', () => {
list.style.display = list.style.display === 'block' ? 'none' : 'block';
});
// 点击外部关闭
document.addEventListener('click', (e) => {
if (!dropdown.contains(e.target)) {
list.style.display = 'none';
}
});
return dropdown;
}
// 创建房屋类型选项
createHouseRoomTypeOptions(selected, dropdown) {
const houseRoomDetailMap = window.MWI_Toolkit?.init_client_data?.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', Utils.getIconHrefBySkillHrid(houseRoomDetail.skillHrid));
svg.appendChild(use);
houseRoomIcon.appendChild(svg);
// 房屋房间名称
const houseRoomName = document.createElement('span');
houseRoomName.textContent = window.MWI_Toolkit?.i18n?.getName(houseRoomDetail.hrid, "houseRoomNames", MWI_Toolkit_Calculator_App.Language) || 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.value = houseRoomDetail.hrid;
optionItem.parentElement.style.display = 'none';
});
// 悬停高亮
optionItem.addEventListener('mouseenter', () => {
optionItem.style.background = '#4a4c6a';
});
optionItem.addEventListener('mouseleave', () => {
optionItem.style.background = 'transparent';
});
optionItem.value = houseRoomDetail.hrid;
return optionItem;
});
}
// 绑定房屋选择相关事件
bindHouseRoomSelectionComponentEvents(dropdown, levelInput, addListButton) {
// 输入框获得焦点时全选内容
levelInput.addEventListener('focus', function () {
setTimeout(() => {
levelInput.select();
}, 0);
});
// 添加按钮事件
addListButton.addEventListener('click', () => {
const houseRoomHrid = dropdown.dataset.value;
const level = parseInt(levelInput.value) || 1;
if (MWI_Toolkit_Calculator_App.EventHandler) {
MWI_Toolkit_Calculator_App.EventHandler.addTargetItem(houseRoomHrid, level);
}
});
}
// 创建目标物品元素
createTargetItemElement(displayItem) {
// 拥有数量
const ownedSpan = document.createElement('span');
ownedSpan.textContent = Utils.formatNumber(displayItem.ownedCount);
ownedSpan.style.padding = '4px 1px';
ownedSpan.style.marginLeft = '4px';
// 斜杠分隔符
const slash = document.createElement('span');
slash.textContent = "/";
slash.style.padding = '4px 1px';
// 可编辑的需求数量输入框
const inputTarget = document.createElement('input');
inputTarget.type = 'text';
inputTarget.value = displayItem.requiredCount;
inputTarget.placeholder = '需求';
inputTarget.style.imeMode = 'disabled';
inputTarget.style.background = '#dde2f8';
inputTarget.style.color = '#000000';
inputTarget.style.border = 'none';
inputTarget.style.borderRadius = '4px';
inputTarget.style.padding = '4px';
inputTarget.style.margin = '2px';
inputTarget.style.width = '60px';
// 绑定输入事件
this.bindInputEvents(inputTarget, displayItem.itemHrid);
// 删除按钮
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.width = '26px';
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', Utils.getIconHrefByMiscHrid('remove'));
removeSvg.appendChild(removeUse);
removeButton.appendChild(removeSvg);
removeButton.addEventListener('click', () => {
if (MWI_Toolkit_Calculator_App.EventHandler) {
MWI_Toolkit_Calculator_App.EventHandler.removeTargetItem(displayItem.itemHrid);
}
});
const itemRow = this.createItemRowBase(displayItem, [ownedSpan, slash, inputTarget, removeButton]);
itemRow.className = 'Toolkit_Calculator_TargetRow';
// 设置DOM引用
displayItem.setDomReferences(itemRow, ownedSpan, inputTarget, null);
return itemRow;
}
// 绑定输入框事件
bindInputEvents(inputElement, itemHrid) {
// 输入框获得焦点时全选内容
inputElement.addEventListener('focus', function () {
setTimeout(() => {
inputElement.select();
}, 0);
});
inputElement.addEventListener('input', function () {
// 清理非数字字符
this.value = this.value.replace(/\D/g, '');
// 直接调用事件处理器(事件处理器内部有防抖)
const newCount = parseInt(this.value) || 0;
if (MWI_Toolkit_Calculator_App.EventHandler) {
MWI_Toolkit_Calculator_App.EventHandler.updateTargetItem(itemHrid, newCount);
}
});
inputElement.addEventListener('blur', function () {
const newCount = parseInt(this.value) || 0;
if (MWI_Toolkit_Calculator_App.EventHandler) {
MWI_Toolkit_Calculator_App.EventHandler.updateTargetItem(itemHrid, newCount);
}
});
inputElement.addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
const newCount = parseInt(this.value) || 0;
if (MWI_Toolkit_Calculator_App.EventHandler) {
MWI_Toolkit_Calculator_App.EventHandler.updateTargetItem(itemHrid, newCount);
}
this.blur();
}
});
}
// 创建缺口物品元素
createMissingItemElement(displayItem) {
const missingSpan = document.createElement('span');
missingSpan.textContent = Utils.formatNumber(displayItem.missingCount);
missingSpan.style.padding = '4px 1px';
missingSpan.style.marginLeft = '4px';
const itemRow = this.createItemRowBase(displayItem, [missingSpan]);
itemRow.className = 'Toolkit_Calculator_ProgressRow';
// 设置DOM引用
displayItem.setDomReferences(itemRow, null, null, missingSpan);
return itemRow;
}
// 创建物品行基础结构
createItemRowBase(displayItem, rightContentNodes) {
const container = document.createElement('div');
container.style.background = '#2c2e45';
container.style.border = 'none';
container.style.borderRadius = '4px';
container.style.padding = '1px 4px';
container.style.margin = '2px';
container.style.display = 'flex';
// 左侧:图标和名称
const left = document.createElement('div');
left.style.minWidth = '40px';
left.style.alignItems = 'center';
left.style.display = 'flex';
left.style.flex = '1';
// 物品图标
const iconContainer = document.createElement('div');
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';
left.appendChild(iconContainer);
left.appendChild(displayNameDiv);
container.appendChild(left);
// 右侧:内容
const right = document.createElement('div');
right.style.display = 'flex';
rightContentNodes.forEach(node => right.appendChild(node));
container.appendChild(right);
return container;
}
// 渲染物品列表
renderItemsDisplay() {
if (!MWI_Toolkit_Calculator_App.Core.targetItems || MWI_Toolkit_Calculator_App.Core.targetItems.length === 0) {
this.clearAllDisplayItems();
return;
}
// 合并重复物品并对列表进行排序
MWI_Toolkit_Calculator_App.Core.targetItems = MWI_Toolkit_Calculator_App.Core.mergeMaterialArrays(MWI_Toolkit_Calculator_App.Core.targetItems, []);
// 更新目标物品区域
this.updateTargetItemsDisplay();
// 更新缺失物品区域
this.updateMissingItemsDisplay();
}
// 更新目标物品显示区域
updateTargetItemsDisplay() {
if (!this.targetItemDiv) return;
// 计算目标物品显示数据
// 开始更新前对目标物品进行了去重和排序,因此targetItems是有序的
const targetItems = MWI_Toolkit_Calculator_App.Core.targetItems;
const ownedItems = MWI_Toolkit_Calculator_App.Core.checkOwnedItems(targetItems);
// 移除不再需要的物品
for (const [itemHrid, displayItem] of this.targetDisplayItems) {
if (!targetItems.some(item => item.itemHrid === itemHrid)) {
if (displayItem.domElement && displayItem.domElement.parentNode) {
displayItem.domElement.parentNode.removeChild(displayItem.domElement);
}
displayItem.destroyDomReferences();
this.targetDisplayItems.delete(itemHrid);
}
}
// 此时this.targetDisplayItems中只包含仍然需要显示的物品
// 使用一个变量指向上一个处理的TargetItemElement
let lastElement = null;
targetItems.forEach(targetItem => {
const ownedItem = ownedItems.find(oi => oi.itemHrid === targetItem.itemHrid);
let displayItem = this.targetDisplayItems.get(targetItem.itemHrid);
const houseRoomLevel = window.MWI_Toolkit?.init_character_data?.characterHouseRoomMap[targetItem.itemHrid]?.level || 0;
const ownedCount = (ownedItem ? ownedItem.count : 0) + houseRoomLevel;
const requiredCount = targetItem.count;
if (displayItem) {
// 更新现有物品
displayItem.updateCounts(ownedCount, requiredCount);
// 不需要确认顺序,创建时顺序正确即可
lastElement = displayItem.domElement;
} else {
// 创建新物品
displayItem = new DisplayItem(targetItem.itemHrid, ownedCount, requiredCount);
const element = this.createTargetItemElement(displayItem);
this.targetDisplayItems.set(targetItem.itemHrid, displayItem);
if (lastElement) {
lastElement.insertAdjacentElement('afterend', element);
} else {
if (this.targetItemDiv.firstChild) {
this.targetItemDiv.firstChild.insertAdjacentElement('beforebegin', element);
} else {
this.targetItemDiv.appendChild(element);
}
}
lastElement = element;
}
});
}
// 更新缺失物品显示区域
updateMissingItemsDisplay() {
if (!this.missingItemDiv) return;
// 计算需求物品显示数据
// batchCalculateRequiredItems返回的requiredItems已经是有序的
const requiredItems = MWI_Toolkit_Calculator_App.Core.batchCalculateRequiredItems(MWI_Toolkit_Calculator_App.Core.targetItems);
const ownedItems = MWI_Toolkit_Calculator_App.Core.checkOwnedItems(requiredItems);
const equivalentItems = MWI_Toolkit_Calculator_App.Core.calculateEquivalentItems(requiredItems, ownedItems);
const missingItems = MWI_Toolkit_Calculator_App.Core.mergeMaterialArrays(requiredItems, equivalentItems);
// 移除不再需要的物品
for (const [itemHrid, displayItem] of this.missingDisplayItems) {
const missingItem = missingItems.find(mi => mi.itemHrid === itemHrid);
if (!missingItem || missingItem.count <= 0) {
if (displayItem.domElement && displayItem.domElement.parentNode) {
displayItem.domElement.parentNode.removeChild(displayItem.domElement);
}
displayItem.destroyDomReferences();
this.missingDisplayItems.delete(itemHrid);
}
}
// 按顺序处理缺失物品
let lastElement = null;
missingItems.forEach(missingItem => {
// 只处理数量大于0的缺失物品
if (missingItem.count <= 0.001) return;
const ownedItem = ownedItems.find(oi => oi.itemHrid === missingItem.itemHrid);
let displayItem = this.missingDisplayItems.get(missingItem.itemHrid);
const ownedCount = ownedItem ? ownedItem.count : 0;
const requiredCount = missingItem.count + ownedCount;
if (displayItem) {
// 更新现有物品
displayItem.updateCounts(ownedCount, requiredCount);
// 不需要确认顺序,创建时顺序正确即可
lastElement = displayItem.domElement;
} else {
// 创建新物品
displayItem = new DisplayItem(missingItem.itemHrid, ownedCount, requiredCount);
const element = this.createMissingItemElement(displayItem);
this.missingDisplayItems.set(missingItem.itemHrid, displayItem);
if (lastElement) {
lastElement.insertAdjacentElement('afterend', element);
} else {
if (this.missingItemDiv.firstChild) {
this.missingItemDiv.firstChild.insertAdjacentElement('beforebegin', element);
} else {
this.missingItemDiv.appendChild(element);
}
}
lastElement = element;
}
});
}
// 清空所有显示项
clearAllDisplayItems() {
// 清空目标物品
for (const [itemHrid, displayItem] of this.targetDisplayItems) {
displayItem.destroyDomReferences();
}
this.targetDisplayItems.clear();
if (this.targetItemDiv) {
this.targetItemDiv.innerHTML = '';
}
// 清空需求物品
for (const [itemHrid, displayItem] of this.missingDisplayItems) {
displayItem.destroyDomReferences();
}
this.missingDisplayItems.clear();
if (this.missingItemDiv) {
this.missingItemDiv.innerHTML = '';
}
}
}
//#endregion
//#region 事件处理器
class MWI_Toolkit_Calculator_EventHandler {
constructor() {
this.renderTimeout = null;
}
// 添加目标物品
addTargetItem(itemHrid, count = 1) {
if (!itemHrid || count <= 0) return;
const existingItemIndex = MWI_Toolkit_Calculator_App.Core.targetItems.findIndex(item => item.itemHrid === itemHrid);
if (existingItemIndex !== -1) {
// 如果物品已存在,增加数量
if (itemHrid.includes('/items/')) {
MWI_Toolkit_Calculator_App.Core.targetItems[existingItemIndex].count += count;
}
if (itemHrid.includes('/house_rooms/')) {
MWI_Toolkit_Calculator_App.Core.targetItems[existingItemIndex].count = Math.max(MWI_Toolkit_Calculator_App.Core.targetItems[existingItemIndex].count, count);
}
} else {
// 添加新物品
MWI_Toolkit_Calculator_App.Core.targetItems.push(new Item(itemHrid, count));
}
this.saveAndScheduleRender('数据已保存');
}
// 更新目标物品
updateTargetItem(itemHrid, newCount) {
if (!itemHrid) return;
if (newCount < 0) newCount = 0;
const existingItemIndex = MWI_Toolkit_Calculator_App.Core.targetItems.findIndex(item => item.itemHrid === itemHrid);
if (existingItemIndex !== -1) {
MWI_Toolkit_Calculator_App.Core.targetItems[existingItemIndex].count = newCount;
} else if (newCount > 0) {
// 如果物品不存在且数量大于0,添加新物品
MWI_Toolkit_Calculator_App.Core.targetItems.push(new Item(itemHrid, newCount));
}
this.saveAndScheduleRender('数据已保存');
}
// 删除目标物品
removeTargetItem(itemHrid) {
if (!itemHrid) return;
const index = MWI_Toolkit_Calculator_App.Core.targetItems.findIndex(item => item.itemHrid === itemHrid);
if (index !== -1) {
MWI_Toolkit_Calculator_App.Core.targetItems.splice(index, 1);
this.saveAndScheduleRender('数据已保存');
}
}
// 清空所有目标物品
clearAllTargetItems() {
MWI_Toolkit_Calculator_App.Core.targetItems = [];
// 清空保存的数据
MWI_Toolkit_Calculator_App.DataManager.clearSavedData();
this.scheduleRender();
}
// 保存数据并计划渲染
saveAndScheduleRender() {
// 保存数据到存储
MWI_Toolkit_Calculator_App.DataManager.saveTargetItems(MWI_Toolkit_Calculator_App.Core.targetItems);
this.scheduleRender();
}
// 计划延迟渲染
scheduleRender() {
// 清除之前的计时器
if (this.renderTimeout) {
clearTimeout(this.renderTimeout);
}
// 设置新的计时器
this.renderTimeout = setTimeout(() => {
MWI_Toolkit_Calculator_App.UIManager.renderItemsDisplay();
this.renderTimeout = null;
}, 300); // 300ms 防抖延迟
}
// 注册物品变更监听器
registerItemChangeListener() {
// 注册物品变更监听器
if (window.MWI_Toolkit?.characterItems?.changeCallbacks) {
window.MWI_Toolkit.characterItems.changeCallbacks.push((endCharacterItems) => {
this.scheduleRender();
});
console.log('[MWI计算器] 物品变更监听器已注册');
} else {
console.warn('[MWI计算器] 无法注册物品变更监听器,MWI_Toolkit.characterItems.changeCallbacks 不可用');
}
}
}
//#endregion
//#region 主应用程序
class MWI_Toolkit_Calculator_App {
static Core;
static DataManager;
static UIManager;
static EventHandler;
static Language;
static isFirstInitialization = true;
constructor() {
MWI_Toolkit_Calculator_App.Core = new MWI_Toolkit_Calculator_Core();
MWI_Toolkit_Calculator_App.DataManager = new MWI_Toolkit_Calculator_DataManager();
MWI_Toolkit_Calculator_App.UIManager = new MWI_Toolkit_Calculator_UIManager();
MWI_Toolkit_Calculator_App.EventHandler = new MWI_Toolkit_Calculator_EventHandler();
}
start() {
this.waitForDependencies(() => {
this.initialize();
});
window.MWI_Toolkit.switchCharacterCallbacks.push(() => {
this.waitForDependencies(() => {
this.initialize();
});
});
}
// 初始化应用程序
initialize() {
console.log('[MWI计算器] 开始初始化...');
if (MWI_Toolkit_Calculator_App.isFirstInitialization) {
// 事件不重复注册
MWI_Toolkit_Calculator_App.EventHandler.registerItemChangeListener();
MWI_Toolkit_Calculator_App.isFirstInitialization = false;
}
// 创建UI
MWI_Toolkit_Calculator_App.UIManager.initialize();
// 读取角色ID
MWI_Toolkit_Calculator_App.DataManager.initStorageKey();
// 加载保存数据
MWI_Toolkit_Calculator_App.DataManager.loadTargetItems();
console.log('[MWI计算器] 初始化完成');
}
// 等待依赖项加载完成
waitForDependencies(callback) {
const checkDependencies = setInterval(() => {
if (window.MWI_Toolkit?.init_character_data &&
window.MWI_Toolkit?.init_client_data) {
clearInterval(checkDependencies);
console.log('[MWI计算器] 依赖项加载完成');
// 等待DOM元素出现
this.waitForElement(callback);
}
}, 500);
}
// 等待DOM元素出现
waitForElement(callback) {
const selector = '[class^="CharacterManagement_tabsComponentContainer"] [class*="TabsComponent_tabsContainer"]';
const el = document.querySelector(selector);
if (el) {
callback();
return;
}
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
callback();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
}
//#endregion
// 创建并启动应用程序实例
const app = new MWI_Toolkit_Calculator_App();
app.start();
})();