Greasy Fork

来自缓存

Greasy Fork is available in English.

拯救deric的懒癌

按角色存储 + 全技能整合收藏 + 多选筛选 + 完整物品卡片(支持跨技能拖拽自定义排序)+ Tab记忆 + 一键清除

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         拯救deric的懒癌
// @namespace    https://www.milkywayidle.com/
// @version      0.512
// @description  按角色存储 + 全技能整合收藏 + 多选筛选 +  完整物品卡片(支持跨技能拖拽自定义排序)+ Tab记忆 + 一键清除
// @author       baozhi powerby_Grok
// @match        https://www.milkywayidle.com/*
// @match        https://www.milkywayidlecn.com/*
// @match        https://test.milkywayidle.com/*
// @grant        none
// @icon         https://www.milkywayidle.com/favicon.svg
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // 1. 扩展允许的技能列表,新增 '挤奶' 和 '伐木'
    const allowedSkills = ['采摘', '奶酪锻造', '制作', '缝纫', '烹饪', '冲泡', '挤奶', '伐木'];

    // 统一存储键
    const MWI_MAIN_STORAGE_KEY = 'mwi_main_data_';

    // 旧的存储键(用于数据迁移)
    const OLD_STORAGE_KEY = 'mwi_all_favorites_';
    const OLD_LAST_TAB_KEY = 'mwi_last_tabs_';
    const OLD_SELECTED_SKILLS_KEY = 'mwi_selected_skills_';
    const CACHE_KEY = 'mwi_item_cache_';

    const PENDING_CLICK_KEY = 'mwi_pending_global_click';

    let currentCharacterId = null;
    let selectedSkills = new Set();
    let isProcessingClick = false;
    let updateTimeout;
    let tabRestoreTimeout;
    let lastObservedSkill = null; // V4.1: 新增状态跟踪,用于判断是否为新的技能面板加载

    // ==================== 辅助函数:防抖 ====================
    /**
     * 防抖更新 UI,用于合并短时间内的多次收藏/取消收藏操作。
     */
    function debounceUpdate() {
        clearTimeout(updateTimeout);
        updateTimeout = setTimeout(() => {
            updateFavoritesPanelIfOpen();
            updateAllFavoriteButtons();
        }, 200);
    }

    // ==================== 角色ID获取 ====================
    function hookCharacterId() {
        const originalGet = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data").get;
        function hookedGet() {
            const result = originalGet.call(this);
            if (this.currentTarget instanceof WebSocket) {
                try {
                    const msg = JSON.parse(result);
                    if (msg?.type === "init_character_data" && msg.character?.id) {
                        const newId = msg.character.id.toString();
                        if (newId !== currentCharacterId) {
                            currentCharacterId = newId;
                            setTimeout(() => {
                                selectedSkills = new Set(getSelectedSkills());
                                debounceUpdate();
                            }, 300);
                        }
                    }
                } catch (e) { /* ignore */ }
            }
            return result;
        }
        Object.defineProperty(MessageEvent.prototype, "data", {
            get: hookedGet,
            configurable: true
        });
    }

    function getCharacterId() {
        if (currentCharacterId) return currentCharacterId;
        if (window.mwi?.character?.id) return window.mwi.character.id.toString();
        return 'default';
    }

    // ==================== 统一存储操作 (主存储) ====================
    function getMainStorageKey() {
        return `${MWI_MAIN_STORAGE_KEY}${getCharacterId()}`;
    }

    // 将 allFavorites 结构根据 globalOrder 同步,以确保数据一致性 (谁拥有哪个物品)
    function syncFavoritesToGlobalOrder(data) {
        // 1. 映射所有现有物品到它们的技能
        const itemToSkillMap = {};
        for (const skill in data.allFavorites) {
            data.allFavorites[skill].forEach(name => {
                itemToSkillMap[name] = skill;
            });
        }

        // 2. 清空 allFavorites 并根据 globalOrder 和 itemToSkillMap 重建
        const newFavorites = {};
        allowedSkills.forEach(skill => newFavorites[skill] = []);

        data.globalOrder.forEach(name => {
            const skill = itemToSkillMap[name]; // 使用物品的原始技能
            if (skill && allowedSkills.includes(skill)) {
                newFavorites[skill].push(name);
            }
        });

        data.allFavorites = newFavorites;
    }

    // 获取所有角色数据,并处理迁移和 globalOrder 初始化
    function getCharacterData() {
        const key = getMainStorageKey();
        let data = {
            allFavorites: {},
            globalOrder: [],
            lastTabs: {},
            selectedSkills: []
        };

        try {
            const raw = localStorage.getItem(key);
            if (raw) {
                data = JSON.parse(raw);
            }
        } catch (e) { /* ignore */ }

        allowedSkills.forEach(skill => {
            data.allFavorites[skill] = data.allFavorites[skill] || [];
        });

        data.globalOrder = data.globalOrder || [];

        const isMigrated = migrateOldData(data);

        // 如果 globalOrder 是空的,则用 allFavorites 初始化它 (保持 skill -> item 顺序)
        if (data.globalOrder.length === 0) {
            let uniqueItems = new Set();
            allowedSkills.forEach(skill => {
                (data.allFavorites[skill] || []).forEach(name => {
                    if (!uniqueItems.has(name)) {
                        data.globalOrder.push(name);
                        uniqueItems.add(name);
                    }
                });
            });
            if (data.globalOrder.length > 0) {
                 setCharacterData(data);
            }
        } else if (isMigrated) {
            syncFavoritesToGlobalOrder(data);
            setCharacterData(data);
        }

        return data;
    }

    function setCharacterData(data) {
        localStorage.setItem(getMainStorageKey(), JSON.stringify(data));
    }

    function migrateOldData(data) {
        let isMigrated = false;

        const oldFavs = localStorage.getItem(`${OLD_STORAGE_KEY}${getCharacterId()}`);
        if (oldFavs) {
            try {
                const parsed = JSON.parse(oldFavs);
                const fullFavorites = {};
                allowedSkills.forEach(skill => {
                    fullFavorites[skill] = parsed[skill] || [];
                });
                data.allFavorites = fullFavorites;
                localStorage.removeItem(`${OLD_STORAGE_KEY}${getCharacterId()}`);
                isMigrated = true;
            } catch (e) { /* ignore */ }
        }

        const oldSelected = localStorage.getItem(`${OLD_SELECTED_SKILLS_KEY}${getCharacterId()}`);
        if (oldSelected) {
            try {
                data.selectedSkills = JSON.parse(oldSelected);
                localStorage.removeItem(`${OLD_SELECTED_SKILLS_KEY}${getCharacterId()}`);
                isMigrated = true;
            } catch (e) { /* ignore */ }
        }

        const oldTabs = localStorage.getItem(`${OLD_LAST_TAB_KEY}${getCharacterId()}`);
        if (oldTabs) {
            try {
                data.lastTabs = JSON.parse(oldTabs);
                localStorage.removeItem(`${OLD_LAST_TAB_KEY}${getCharacterId()}`);
                isMigrated = true;
            } catch (e) { /* ignore */ }
        }

        return isMigrated;
    }

    // ==================== 全局排序操作 ====================

    function getGlobalOrder() {
        return getCharacterData().globalOrder;
    }

    function setGlobalOrder(newOrderArray) {
        const data = getCharacterData();
        data.globalOrder = newOrderArray;
        syncFavoritesToGlobalOrder(data);
        setCharacterData(data);
    }

    function findItemSkill(itemName) {
        const allFavorites = getCharacterData().allFavorites;
        for (const [skill, items] of Object.entries(allFavorites)) {
            if (items.includes(itemName)) {
                return skill;
            }
        }
        return null;
    }

    function getFavoritesForSkill(skill) {
        return getCharacterData().allFavorites[skill] || [];
    }

    function setFavoritesForSkill(skill, arr) {
        const data = getCharacterData();
        data.allFavorites[skill] = arr;
        setCharacterData(data);
    }

    function addToGlobalOrder(name) {
        const data = getCharacterData();
        if (!data.globalOrder.includes(name)) {
            data.globalOrder.push(name);
            syncFavoritesToGlobalOrder(data);
            setCharacterData(data);
        }
    }

    function removeFromGlobalOrder(name) {
        const data = getCharacterData();
        const index = data.globalOrder.indexOf(name);
        if (index > -1) {
            data.globalOrder.splice(index, 1);
            syncFavoritesToGlobalOrder(data);
            setCharacterData(data);
        }
    }

    function saveSelectedSkills(skills) {
        const data = getCharacterData();
        data.selectedSkills = skills;
        setCharacterData(data);
    }

    function getSelectedSkills() {
        const data = getCharacterData();

        const allFavorites = data.allFavorites;
        const skillsWithFavorites = allowedSkills.filter(skill => (allFavorites[skill]?.length || 0) > 0);

        let validSkills = [];
        if (data.selectedSkills && data.selectedSkills.length > 0) {
            validSkills = data.selectedSkills.filter(skill => skillsWithFavorites.includes(skill));
        }

        if (validSkills.length === 0 && skillsWithFavorites.length > 0) {
            validSkills = skillsWithFavorites;
        }

        return validSkills;
    }

    // ==================== 物品卡片缓存 (独立存储) ====================

    function getCacheKey() {
        return `${CACHE_KEY}${getCharacterId()}`;
    }

    function cacheItemCard(item, skill) {
        const nameEl = item.querySelector('.SkillAction_name__2VPXa');
        if (!nameEl) return;
        const name = nameEl.textContent.trim();

        const isFavorited = !!findItemSkill(name);

        if (!isFavorited) return;

        const cleanItem = item.cloneNode(true);
        cleanItem.querySelector('.mwi-fav-btn')?.remove();

        const clone = cleanItem.cloneNode(true);

        try {
            const cache = JSON.parse(localStorage.getItem(getCacheKey()) || '{}');
            if (!cache[skill]) cache[skill] = {};
            cache[skill][name] = clone.outerHTML;
            localStorage.setItem(getCacheKey(), JSON.stringify(cache));
        } catch (e) { /* ignore */ }
    }

    function getCachedItem(skill, name) {
        try {
            const cache = JSON.parse(localStorage.getItem(getCacheKey()) || '{}');
            let cachedHtml = cache[skill]?.[name] || null;
            if (!cachedHtml) {
                for (const sk in cache) {
                    if (cache[sk][name]) {
                        cachedHtml = cache[sk][name];
                        break;
                    }
                }
            }
            return cachedHtml;
        } catch (e) {
            return null;
        }
    }

    function removeCacheForItem(skill, name) {
        try {
            const cache = JSON.parse(localStorage.getItem(getCacheKey()) || '{}');
            if (cache[skill] && cache[skill][name]) {
                delete cache[skill][name];
                localStorage.setItem(getCacheKey(), JSON.stringify(cache));
            }
        } catch (e) { /* ignore */ }
    }

    // ==================== 收藏按钮(技能页面)====================
    function addFavoriteButton(item, currentSkill) {
        if (item.querySelector('.mwi-fav-btn')) return;
        const nameEl = item.querySelector('.SkillAction_name__2VPXa');
        if (!nameEl) return;
        const name = nameEl.textContent.trim();

        const btn = document.createElement('button');
        btn.className = 'mwi-fav-btn';

        let itemSkill = findItemSkill(name);
        const isFavorited = !!itemSkill;

        btn.textContent = isFavorited ? '⭐' : '☆';

        Object.assign(btn.style, {
            position: 'absolute', top: '4px', right: '4px', background: 'none',
            border: 'none', fontSize: '18px', cursor: 'pointer', zIndex: '10',
            color: isFavorited ? 'rgb(255, 215, 0)' : 'rgb(170, 170, 170)'
        });

        btn.onclick = e => {
            e.stopPropagation();

            const itemSkill = findItemSkill(name);

            if (itemSkill) {
                // 取消收藏
                const arr = getFavoritesForSkill(itemSkill);
                const idx = arr.indexOf(name);
                if (idx > -1) {
                    arr.splice(idx, 1);
                    setFavoritesForSkill(itemSkill, arr);
                    removeFromGlobalOrder(name);
                    removeCacheForItem(itemSkill, name);

                    btn.textContent = '☆';
                    btn.style.color = 'rgb(170, 170, 170)';

                    debounceUpdate();
                    updateOtherSkillFavoriteButtons(name, false);
                }
            } else {
                // 添加收藏到当前技能
                const arr = getFavoritesForSkill(currentSkill);
                if (!arr.includes(name)) {
                    arr.push(name);
                    setFavoritesForSkill(currentSkill, arr);
                    addToGlobalOrder(name);

                    cacheItemCard(item, currentSkill);

                    btn.textContent = '⭐';
                    btn.style.color = 'rgb(255, 215, 0)';

                    debounceUpdate();
                }
            }
        };

        item.style.position = 'relative';
        item.appendChild(btn);
    }

    // 更新其他技能的收藏按钮状态
    function updateOtherSkillFavoriteButtons(itemName, isFavorited = null) {
        if (isFavorited === null) {
            isFavorited = !!findItemSkill(itemName);
        }

        const allItems = document.querySelectorAll('.SkillAction_skillAction__1esCp:not(.SkillAction_opaque__s9Yeq)');

        allItems.forEach(item => {
            const nameEl = item.querySelector('.SkillAction_name__2VPXa');
            if (nameEl && nameEl.textContent.trim() === itemName) {
                const btn = item.querySelector('.mwi-fav-btn');
                if (btn) {
                    btn.textContent = isFavorited ? '⭐' : '☆';
                    btn.style.color = isFavorited ? 'rgba(255, 254, 249, 1)' : 'rgb(170, 170, 170)';
                }
            }
        });
    }

    function updateAllFavoriteButtons() {
        const allItems = document.querySelectorAll('.SkillAction_skillAction__1esCp:not(.SkillAction_opaque__s9Yeq)');
        const currentSkill = document.querySelector('.NavigationBar_active__3R-QS .NavigationBar_label__1uH-y')?.textContent.trim();

        allItems.forEach(item => {
            const btn = item.querySelector('.mwi-fav-btn');
            if (btn) {
                const nameEl = item.querySelector('.SkillAction_name__2VPXa');
                if (nameEl) {
                    const name = nameEl.textContent.trim();
                    const isFavorited = !!findItemSkill(name);

                    btn.textContent = isFavorited ? '⭐' : '☆';
                    btn.style.color = isFavorited ? 'rgb(255, 215, 0)' : 'rgb(170, 170, 170)';

                    if (isFavorited && currentSkill && !getCachedItem(currentSkill, name)) {
                        setTimeout(() => {
                            cacheItemCard(item, currentSkill);
                        }, 10);
                    }
                }
            }
        });
    }

    // ==================== Tab 切换和跨技能跳转 ====================

    function getLastTabIndex(skill) {
        const data = getCharacterData();
        return data.lastTabs[skill] !== undefined ? data.lastTabs[skill] : 0;
    }

    function setLastTabIndex(skill, idx) {
        const data = getCharacterData();
        data.lastTabs[skill] = idx;
        setCharacterData(data);
    }

    /**
     * 为“挤奶”和“伐木”这种没有原生标签页的技能注入 Mui Tab 结构
     */
    function injectTabStructure(root, skill) {
        // 检查是否已经注入
        if (root.querySelector('.MuiTabs-root')) {
            return;
        }

        const tabsComponentContainer = root.querySelector('.GatheringProductionSkillPanel_tabsComponentContainer__3Ua1T');
        if (!tabsComponentContainer) return;

        const tabsComponent = tabsComponentContainer.querySelector('.TabsComponent_tabsComponent__3PqGp');
        const panelsContainer = tabsComponentContainer.querySelector('.TabsComponent_tabPanelsContainer__26mzo');

        if (!tabsComponent || !panelsContainer) return;

        // 1. 创建 Tab 结构
        const tabsContainer = document.createElement('div');
        tabsContainer.className = 'TabsComponent_tabsContainer__3BDUp TabsComponent_wrap__3fEC7';

        const muiTabsRoot = document.createElement('div');
        muiTabsRoot.className = 'MuiTabs-root css-orq8zk'; // 使用通用的 Mui Tabs 类

        const muiTabsScroller = document.createElement('div');
        muiTabsScroller.className = 'MuiTabs-scroller MuiTabs-fixed css-1anid1y';
        muiTabsScroller.style.overflow = 'hidden';
        muiTabsScroller.style.marginBottom = '0px';

        const muiTabsFlexContainer = document.createElement('div');
        muiTabsFlexContainer.className = 'MuiTabs-flexContainer css-k008qs';
        muiTabsFlexContainer.setAttribute('role', 'tablist');

        const indicator = document.createElement('span');
        indicator.className = 'MuiTabs-indicator css-ttwr4n';
        indicator.style.left = '0px';
        indicator.style.width = '0px';

        // 2. 创建第一个 Tab 按钮(原操作列表)
        const originalTabButton = document.createElement('button');
        originalTabButton.className = 'MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary css-1q2h7u5 Mui-selected';
        originalTabButton.setAttribute('role', 'tab');
        originalTabButton.setAttribute('aria-selected', 'true');
        originalTabButton.setAttribute('tabindex', '0');
        // V3.9: 添加自定义标识
        originalTabButton.setAttribute('data-mwi-custom-tab', 'true');
        originalTabButton.innerHTML = `<span class="MuiBadge-root TabsComponent_badge__1Du26 css-1rzb3uu">${skill}<span class="MuiBadge-badge MuiBadge-standard MuiBadge-invisible MuiBadge-anchorOriginTopRight MuiBadge-anchorOriginTopRightRectangular MuiBadge-overlapRectangular css-vwo4eg"></span></span><span class="MuiTouchRipple-root css-w0pj6f"></span>`;

        // 3. 组装 Tab 栏
        muiTabsFlexContainer.appendChild(originalTabButton);
        muiTabsScroller.appendChild(muiTabsFlexContainer);
        muiTabsScroller.appendChild(indicator);
        muiTabsRoot.appendChild(muiTabsScroller);
        tabsContainer.appendChild(muiTabsRoot);

        // 4. 注入 Tab 栏结构
        tabsComponent.insertBefore(tabsContainer, panelsContainer);

        // 5. 调整原有的内容面板:使其成为第一个 Tab 的面板并显示
        const originalPanel = panelsContainer.querySelector('.TabPanel_tabPanel__tXMJF');
        if (originalPanel) {
            // 确保第一个面板可见
            originalPanel.classList.remove('TabPanel_hidden__26UM3');
        }
    }


    // 仅在有 Tab 结构的技能面板中创建“收藏”Tab
    function ensureFavoritesTab(tabsContainer, skill) {
        let favTab = tabsContainer.querySelector('.mwi-fav-tab');
        if (favTab) return;

        const tabsFlex = tabsContainer.querySelector('.MuiTabs-flexContainer');
        if (!tabsFlex) return;

        const tabsComponentContainer = tabsContainer.closest('.GatheringProductionSkillPanel_tabsComponentContainer__3Ua1T');
        if (!tabsComponentContainer) return;

        const panelsContainer = tabsComponentContainer.querySelector('.TabsComponent_tabPanelsContainer__26mzo');
        if (!panelsContainer) return;

        // --- 1. 创建 Tab 按钮 ---
        const tab = document.createElement('button');
        tab.className = 'MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary css-1q2h7u5 mwi-fav-tab';
        tab.setAttribute('role', 'tab');
        tab.setAttribute('aria-selected', 'false');
        tab.setAttribute('tabindex', '-1');
        // V3.9: 添加自定义标识
        tab.setAttribute('data-mwi-custom-tab', 'true');
        tab.innerHTML = `<span style="position:relative; display:inline-flex; align-items:center;">收藏</span><span class="MuiTouchRipple-root css-w0pj6f"></span>`;
        tabsFlex.appendChild(tab);

        // --- 2. 创建 Tab 面板 ---
        const panel = document.createElement('div');
        panel.className = 'TabPanel_tabPanel__tXMJF TabPanel_hidden__26UM3 mwi-fav-panel';
        panel.style.padding = '24px';
        panel.style.position = 'relative';
        panelsContainer.appendChild(panel);
    }

    function switchToTab(skill, targetIdx) {
        const root = document.querySelector('.GatheringProductionSkillPanel_gatheringProductionSkillPanel__vG4M7');
        if (!root) return;

        const tabsContainer = root.querySelector('.MuiTabs-root');
        if (!tabsContainer) return;

        const tabsFlex = tabsContainer.querySelector('.MuiTabs-flexContainer');
        if (!tabsFlex) return;

        const tabs = Array.from(tabsFlex.children);
        const indicator = tabsContainer.querySelector('.MuiTabs-indicator');
        // 确保获取到所有面板,包括原生的和自定义的
        const panels = root.querySelectorAll('.TabsComponent_tabPanelsContainer__26mzo > .TabPanel_tabPanel__tXMJF');

        // 再次检查边界,防止出现越界错误
        if (targetIdx < 0 || targetIdx >= tabs.length) {
             console.error(`[MWI Fav] Invalid target index: ${targetIdx} for skill ${skill}`);
             return;
        }

        // 更新按钮状态
        tabs.forEach((t, i) => {
            t.classList.toggle('Mui-selected', i === targetIdx);
            t.setAttribute('aria-selected', i === targetIdx);
            t.setAttribute('tabindex', i === targetIdx ? '0' : '-1');
        });

        // 更新面板状态 (原生面板 + 收藏面板)
        panels.forEach((p, i) => {
            p.classList.toggle('TabPanel_hidden__26UM3', i !== targetIdx);
        });

        // 更新指示器位置
        if (indicator && tabs[targetIdx]) {
            // 计算目标 Tab 之前所有 Tab 的宽度总和 (用于确定 translateX)
            const left = tabs.slice(0, targetIdx).reduce((s, t) => s + t.offsetWidth, 0);

            const tabsRoot = root.querySelector('.MuiTabs-root');
            // 获取 MuiTabs-root 的滚动位置,确保指示器在滚动时位置正确
            const scrollLeft = tabsRoot ? tabsRoot.scrollLeft : 0;

            const indicatorX = left - scrollLeft;
            const indicatorW = tabs[targetIdx].offsetWidth;

            indicator.style.transform = `translateX(${indicatorX}px)`;
            indicator.style.width = `${indicatorW}px`;

            // 确保指示器的颜色也正确 (游戏原始颜色)
            indicator.style.backgroundColor = 'rgb(240, 240, 240)';
        }

        // 关键:保存正确的 Tab 索引
        setLastTabIndex(skill, targetIdx);
        if (targetIdx === tabs.length - 1 && targetIdx >= 0) {
            updateFavoritesPanel(skill);
        }
    }

    function setPendingClick(skill, itemName, selectedSkillsForTarget) {
        if (isProcessingClick) return;

        sessionStorage.setItem(PENDING_CLICK_KEY, JSON.stringify({
            skill,
            itemName,
            selectedSkills: Array.from(selectedSkillsForTarget)
        }));
    }

    function getPendingClick() {
        const data = sessionStorage.getItem(PENDING_CLICK_KEY);
        sessionStorage.removeItem(PENDING_CLICK_KEY);
        return data ? JSON.parse(data) : null;
    }

    function handlePendingClick() {
        if (isProcessingClick) return;

        const pending = getPendingClick();
        if (!pending) return;

        const { skill, itemName, selectedSkills: savedSelectedSkills } = pending;

        const currentSkillLabel = document.querySelector('.NavigationBar_active__3R-QS .NavigationBar_label__1uH-y');
        if (!currentSkillLabel || currentSkillLabel.textContent.trim() !== skill) return;

        isProcessingClick = true;

        if (savedSelectedSkills && savedSelectedSkills.length > 0) {
            selectedSkills.clear();
            savedSelectedSkills.forEach(s => selectedSkills.add(s));
        }

        // V4.0: 使用 setTimeout 延迟,确保页面导航和 Tab 结构加载完成
        setTimeout(() => {
            try {
                const root = document.querySelector('.GatheringProductionSkillPanel_gatheringProductionSkillPanel__vG4M7');

                // 确保 Tab 结构已存在,特别是对于挤奶/伐木
                if ((skill === '挤奶' || skill === '伐木') && !root.querySelector('.MuiTabs-root')) {
                    injectTabStructure(root, skill);
                }

                const tabsContainer = root?.querySelector('.MuiTabs-root');

                // 只有有 tab 的技能才需要切换 tab
                if (tabsContainer) {
                    const tabsFlex = tabsContainer.querySelector('.MuiTabs-flexContainer');
                    if (tabsFlex) {
                        const favTabIdx = tabsFlex.children.length - 1;
                        const favTab = tabsFlex.children[favTabIdx];
                        if (favTab) {
                            // 强制切换到收藏页
                            switchToTab(skill, favTabIdx);
                        }
                    }
                }

                setTimeout(() => {
                    clickTargetItem(itemName, skill);
                    isProcessingClick = false;
                }, 50); // 再次延迟 50ms 确保收藏 Tab 内容渲染完毕
            } catch (e) {
                isProcessingClick = false;
            }
        }, 100); // 延迟 100ms 等待技能页面完全加载
    }

    function clickTargetItem(itemName, skill) {
        const itemEl = Array.from(document.querySelectorAll('.SkillAction_skillAction__1esCp')).find(item =>
            item.querySelector('.SkillAction_name__2VPXa')?.textContent.trim() === itemName
        );

        if (itemEl && !itemEl.classList.contains('SkillAction_opaque__s9Yeq')) {
            itemEl.click();
        }
    }

    // ==================== 收藏面板(多选版本)====================
    function updateFavoritesPanel(currentSkill) {
        const panel = document.querySelector('.mwi-fav-panel');
        if (!panel) return;

        const scrollTop = panel.scrollTop;
        panel.innerHTML = '';

        const allFavorites = getCharacterData().allFavorites;
        const skillCounts = allowedSkills.reduce((acc, skill) => {
            acc[skill] = allFavorites[skill]?.length || 0;
            return acc;
        }, {});

        const skillsWithFavorites = allowedSkills.filter(skill => skillCounts[skill] > 0);

        // --- 技能选择逻辑 ---
        if (selectedSkills.size === 0) {
            const savedSkills = getSelectedSkills();
            savedSkills.forEach(skill => selectedSkills.add(skill));
            if (selectedSkills.size > 0 && savedSkills.length !== Array.from(selectedSkills).length) {
                 saveSelectedSkills(Array.from(selectedSkills));
            }
        } else {
            selectedSkills.forEach(skill => {
                if (!skillsWithFavorites.includes(skill)) {
                    selectedSkills.delete(skill);
                }
            });
            if (selectedSkills.size === 0 && skillsWithFavorites.length > 0) {
                 selectedSkills.add(skillsWithFavorites[0]);
            }
            saveSelectedSkills(Array.from(selectedSkills));
        }

        // --- 筛选容器 ---
        const filterContainer = document.createElement('div');
        filterContainer.style.marginBottom = '20px';
        filterContainer.style.display = 'flex';
        filterContainer.style.gap = '8px';
        filterContainer.style.flexWrap = 'wrap';
        filterContainer.style.alignItems = 'center';
        filterContainer.className = 'mwi-filter-container';

        const createFilterButton = (text, isSelected, clickHandler) => {
            const btn = document.createElement('button');
            btn.className = 'MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary mwi-filter-btn';
            btn.textContent = text;
            Object.assign(btn.style, {
                minWidth: '0', padding: '6px 12px', margin: '0', border: 'none', cursor: 'pointer',
                background: isSelected ? '#3a3a3a' : 'none', color: isSelected ? 'white' : 'rgba(255, 255, 255, 0.7)',
                borderRadius: '4px', transition: 'all 0.2s', fontWeight: isSelected ? '500' : '400',
                textTransform: 'none'
            });
            btn.addEventListener('mouseenter', () => { btn.style.background = isSelected ? '#3a3a3a' : 'rgba(255, 255, 255, 0.08)'; });
            btn.addEventListener('mouseleave', () => { btn.style.background = isSelected ? '#3a3a3a' : 'none'; });
            btn.addEventListener('click', clickHandler);
            return btn;
        };

        const isAllSelected = selectedSkills.size === skillsWithFavorites.length && skillsWithFavorites.length > 0;
        filterContainer.appendChild(createFilterButton('全选', isAllSelected, () => {
            if (isAllSelected) { selectedSkills.clear(); } else { selectedSkills.clear(); skillsWithFavorites.forEach(skill => selectedSkills.add(skill)); }
            saveSelectedSkills(Array.from(selectedSkills)); updateFavoritesPanel(currentSkill);
        }));

        filterContainer.appendChild(createFilterButton('反选', false, () => {
            const allSelected = Array.from(selectedSkills);
            selectedSkills.clear();
            skillsWithFavorites.forEach(skill => {
                if (!allSelected.includes(skill)) { selectedSkills.add(skill); }
            });
            if (selectedSkills.size === 0 && skillsWithFavorites.length > 0) { selectedSkills.add(skillsWithFavorites[0]); }
            saveSelectedSkills(Array.from(selectedSkills)); updateFavoritesPanel(currentSkill);
        }));

        skillsWithFavorites.forEach(skill => {
            const isSelected = selectedSkills.has(skill);
            filterContainer.appendChild(createFilterButton(`${skill} (${skillCounts[skill]})`, isSelected, () => {
                if (selectedSkills.has(skill)) { if (selectedSkills.size > 1) { selectedSkills.delete(skill); } } else { selectedSkills.add(skill); }
                saveSelectedSkills(Array.from(selectedSkills)); updateFavoritesPanel(currentSkill);
            }));
        });

        panel.appendChild(filterContainer);

        // 清空选中收藏按钮
        const clearBtn = document.createElement('button');
        clearBtn.className = 'mwi-clear-favs-btn';
        clearBtn.textContent = '×';
        clearBtn.title = '清空选中技能的收藏';
        Object.assign(clearBtn.style, {
            position: 'absolute', top: '4px', right: '4px', background: 'none', border: 'none',
            color: '#999', fontSize: '20px', cursor: 'pointer', width: '24px', height: '24px',
            borderRadius: '12px', display: 'flex', alignItems: 'center', justifyContent: 'center',
            padding: '0', zIndex: '100', opacity: '0.5', transition: 'opacity 0.2s'
        });

        clearBtn.addEventListener('mouseenter', () => { clearBtn.style.opacity = '1'; clearBtn.style.color = '#ff4444'; });
        clearBtn.addEventListener('mouseleave', () => { clearBtn.style.opacity = '0.5'; clearBtn.style.color = '#999'; });

        clearBtn.addEventListener('click', () => {
            if (selectedSkills.size === 0) return;

            const skillsList = Array.from(selectedSkills).join('、');
            if (confirm(`确定要清空选中技能(${skillsList})的所有收藏吗?`)) {

                selectedSkills.forEach(skill => {
                    getFavoritesForSkill(skill)?.forEach(name => {
                         removeFromGlobalOrder(name);
                         removeCacheForItem(skill, name);
                    });
                    setFavoritesForSkill(skill, []);
                });

                debounceUpdate();
                selectedSkills.clear();
                saveSelectedSkills(Array.from(selectedSkills));
                updateFavoritesPanel(currentSkill);
            }
        });

        panel.appendChild(clearBtn);

        // 创建物品容器
        const grid = document.createElement('div');
        grid.className = 'SkillActionGrid_skillActionGrid__1tJFk';
        panel.appendChild(grid);

        // 使用 globalOrder 作为排序来源
        const globalOrder = getGlobalOrder();
        const displayItems = [];

        globalOrder.forEach(name => {
            const originalSkill = findItemSkill(name);
            if (originalSkill && selectedSkills.has(originalSkill)) {
                displayItems.push({ name: name, skill: originalSkill });
            }
        });

        if (displayItems.length === 0) {
            grid.innerHTML = `<div style="color:#aaa;padding:40px;text-align:center;">
                                ${skillsWithFavorites.length === 0 ? '暂无任何收藏物品' : (selectedSkills.size === 0 ? '请选择要显示的技能' : '选中的技能暂无收藏物品')}
                            </div>`;
            return;
        }

        // 获取仓库物品数据
        const inventoryItems = getInventoryItems();
        
        // 显示物品卡片
        displayItems.forEach((item) => {
            let clone;
            const cachedHtml = getCachedItem(item.skill, item.name);
            if (cachedHtml) {
                const tempDiv = document.createElement('div');
                tempDiv.innerHTML = cachedHtml;
                clone = tempDiv.firstChild;
            } else {
                clone = createSimpleItemCard(item.name);
            }

            if (!clone || clone.nodeType !== 1) { clone = createSimpleItemCard(item.name); }

            clone.dataset.name = item.name;
            clone.dataset.skill = item.skill;
            clone.style.position = 'relative';
            clone.style.opacity = '1';
            
            // 获取物品的仓库数量
            const itemCount = inventoryItems[item.name] || '0';
            
            // 移除已存在的数量显示元素(如果有)
            const existingCount = clone.querySelector('.mwi-item-count');
            if (existingCount) {
                existingCount.remove();
            }
            
            // 创建数量显示元素
            const countElement = document.createElement('div');
            countElement.className = 'mwi-item-count';
            countElement.textContent = itemCount;
            Object.assign(countElement.style, {
                position: 'absolute',
                bottom: '10px', // 垂直上移
                left: '0',
                right: '0',
                width: '100%', // 水平居中
                background: 'none', // 无背景
                color: '#cccccc', // 浅灰色
                fontSize: '11px',
                fontWeight: 'bold',
                padding: '1px 4px',
                textAlign: 'center',
                zIndex: '5'
            });
            
            // 将数量显示元素添加到物品卡片中
            clone.appendChild(countElement);

            const existingBtns = clone.querySelectorAll('.mwi-fav-btn');
            existingBtns.forEach(btn => btn.remove());

            const favBtn = document.createElement('button');
            favBtn.className = 'mwi-fav-btn mwi-panel-fav-btn';
            favBtn.textContent = '⭐';
            Object.assign(favBtn.style, {
                position: 'absolute', top: '4px', right: '4px', background: 'none',
                border: 'none', fontSize: '18px', cursor: 'pointer', zIndex: '10',
                color: 'rgb(255, 215, 0)'
            });

            favBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                const itemSkill = findItemSkill(item.name);
                if (itemSkill) {
                    const arr = getFavoritesForSkill(itemSkill);
                    const index = arr.indexOf(item.name);
                    if (index > -1) {
                        arr.splice(index, 1);
                        setFavoritesForSkill(itemSkill, arr);
                        removeFromGlobalOrder(item.name);
                        removeCacheForItem(itemSkill, item.name);

                        updateFavoritesPanel(currentSkill);
                        debounceUpdate();
                    }
                }
            });

            clone.appendChild(favBtn);
            clone.addEventListener('click', (e) => {
                if (e.target.closest('.mwi-fav-btn')) return;
                e.stopPropagation();

                const itemSkill = item.skill;
                const currentActiveSkill = document.querySelector('.NavigationBar_active__3R-QS .NavigationBar_label__1uH-y')?.textContent.trim();

                if (itemSkill === currentActiveSkill) {
                    const itemEl = Array.from(document.querySelectorAll('.SkillAction_skillAction__1esCp')).find(itemEl =>
                        itemEl.querySelector('.SkillAction_name__2VPXa')?.textContent.trim() === item.name
                    );
                    if (itemEl) itemEl.click();
                } else {
                    setPendingClick(itemSkill, item.name, selectedSkills);

                    const skillNavs = document.querySelectorAll('.NavigationBar_button__3L1eA, .NavigationBar_navigationLink__3eAHA');
                    const targetNav = Array.from(skillNavs).find(nav => {
                        const label = nav.querySelector('.NavigationBar_label__1uH-y');
                        return label && label.textContent.trim() === itemSkill;
                    });

                    if (targetNav) targetNav.click();
                }
            });

            // --- 跨技能拖拽排序逻辑 ---
            clone.draggable = true;

            clone.addEventListener('dragstart', (e) => {
                e.dataTransfer.setData('text/plain', JSON.stringify({ name: item.name }));
                clone.classList.add('dragging');
                clone.style.opacity = '0.4';
            });

            clone.addEventListener('dragend', () => {
                clone.classList.remove('dragging');
                clone.style.opacity = '1';
                grid.querySelectorAll('.SkillAction_skillAction__1esCp').forEach(c => { c.style.border = ''; });
            });

            clone.addEventListener('dragenter', (e) => {
                e.preventDefault();
                if (clone.classList.contains('dragging')) return;
                clone.style.border = '2px solid #555';
            });

            clone.addEventListener('dragleave', () => {
                clone.style.border = '';
            });

            grid.appendChild(clone);
        });

        // 拖拽释放(处理跨技能排序)
        grid.addEventListener('dragover', (e) => e.preventDefault());

        grid.addEventListener('drop', (e) => {
            e.preventDefault();

            const draggedCard = document.querySelector('.SkillAction_skillAction__1esCp.dragging');
            if (draggedCard) draggedCard.style.opacity = '1';

            try {
                const data = JSON.parse(e.dataTransfer.getData('text/plain'));
                const draggedName = data.name;

                const target = e.target.closest('.SkillAction_skillAction__1esCp');
                if (!target || target.dataset.name === draggedName) return;

                const targetName = target.dataset.name;

                const currentGlobalOrder = getGlobalOrder();

                const fromIndex = currentGlobalOrder.indexOf(draggedName);
                const toIndex = currentGlobalOrder.indexOf(targetName);

                if (fromIndex > -1 && toIndex > -1) {
                    const [movedItem] = currentGlobalOrder.splice(fromIndex, 1);
                    currentGlobalOrder.splice(toIndex, 0, movedItem);

                    setGlobalOrder(currentGlobalOrder);

                    updateFavoritesPanel(currentSkill);
                }
            } catch (e) {
                console.error("拖拽错误:", e);
             } finally {
                grid.querySelectorAll('.SkillAction_skillAction__1esCp').forEach(c => { c.style.border = ''; });
             }
        });

        panel.scrollTop = scrollTop;
    }

    function createSimpleItemCard(itemName) {
        const card = document.createElement('div');
        card.className = 'SkillAction_skillAction__1esCp';
        card.innerHTML = `<div class="SkillAction_container__2Qbcu" style="height: 100%;">
                <div class="SkillAction_iconWrapper__mjk5b" style="width: 48px; height: 48px;">
                    <div class="SkillAction_iconBackground__3LSVo"></div>
                    <div class="SkillAction_icon__3JYqX" style="font-size: 24px;">⚠️</div>
                </div>
                <div class="SkillAction_textContent__2k4Vw">
                    <div class="SkillAction_name__2VPXa">${itemName}</div>
                    <div class="SkillAction_description__1q5Xp" style="color: #ffaa00; font-size: 12px;">缓存丢失</div>
                </div>
            </div>`;
        card.style.cursor = 'pointer';
        return card;
    }

    function updateFavoritesPanelIfOpen() {
        const panel = document.querySelector('.mwi-fav-panel');
        if (panel && !panel.classList.contains('TabPanel_hidden__26UM3')) {
            const skill = document.querySelector('.NavigationBar_active__3R-QS .NavigationBar_label__1uH-y')?.textContent.trim();
            if (skill && allowedSkills.includes(skill)) {
                // 只有 Tabbed 技能才需要更新面板
                if (document.querySelector('.GatheringProductionSkillPanel_gatheringProductionSkillPanel__vG4M7')) {
                     updateFavoritesPanel(skill);
                }
            }
        }
    }

    // ==================== 仓库物品数量获取 ====================
    /**
     * 获取仓库中所有物品的名称和数量
     * @returns {Object} 物品名称到数量的映射
     */
    // 仓库物品数据缓存
    let inventoryDataCache = {};

    // 从DOM中获取仓库物品数据
    function getInventoryItems() {
        const inventory = {};
        
        // 查找仓库容器(使用更通用的选择器,不依赖其他脚本添加的类)
        const inventoryContainer = document.querySelector('.Inventory_items__6SXv0');
        if (!inventoryContainer) {
            return inventory;
        }
        
        // 遍历所有物品(不依赖分类结构,直接查找所有物品容器)
        const allItems = inventoryContainer.querySelectorAll('.Item_itemContainer__x7kH1');
        
        allItems.forEach((item, index) => {
            // 获取物品名称(检查所有可能的路径)
            let iconContainer = item.querySelector('.Item_iconContainer__5z7j4');
            if (!iconContainer) {
                // 尝试更深层的路径
                iconContainer = item.querySelector('.Item_item__2De2O .Item_iconContainer__5z7j4');
            }
            if (!iconContainer) {
                return;
            }
            
            // 首先尝试直接从iconContainer获取aria-label
            let itemName = iconContainer.getAttribute('aria-label');
            
            // 如果没找到,检查内部的svg元素
            if (!itemName) {
                const svgElement = iconContainer.querySelector('svg[role="img"]');
                if (svgElement) {
                    itemName = svgElement.getAttribute('aria-label');
                }
            }
            
            // 如果还是没找到,尝试检查所有子元素
            if (!itemName) {
                const childWithAriaLabel = iconContainer.querySelector('[aria-label]');
                if (childWithAriaLabel) {
                    itemName = childWithAriaLabel.getAttribute('aria-label');
                }
            }
            
            if (!itemName) {
                return;
            }
            
            // 获取物品数量
            let countElement = item.querySelector('.Item_count__1HVvv');
            if (!countElement) {
                // 尝试更深层的路径
                countElement = item.querySelector('.Item_item__2De2O .Item_count__1HVvv');
            }
            const count = countElement ? countElement.textContent.trim() : '0';
            
            // 存储到映射中
            inventory[itemName] = count;
        });
        
        // 更新缓存
        inventoryDataCache = inventory;
        return inventory;
    }

    // 监听仓库变化的Observer
    const inventoryObserver = new MutationObserver(() => {
        getInventoryItems();
        
        // 仓库数据变化时,自动更新收藏面板的物品数量显示
        updateFavoritesPanelIfOpen();
    });

    // 启动仓库监听
    function startInventoryObserver() {
        // 先尝试直接获取一次数据
        getInventoryItems();
        
        // 设置定时器定期检查仓库面板是否打开
        const checkInventoryInterval = setInterval(() => {
            const inventoryContainer = document.querySelector('.Inventory_items__6SXv0');
            if (inventoryContainer) {
                // 观察仓库容器的变化
                inventoryObserver.observe(inventoryContainer, {
                    childList: true,
                    subtree: true,
                    characterData: true
                });
                
                // 清除定时器
                clearInterval(checkInventoryInterval);
            }
        }, 2000); // 每2秒检查一次
    }

    // ==================== 主循环 ====================
    hookCharacterId();
    selectedSkills = new Set(getSelectedSkills());
    
    // 启动仓库数据监听
    startInventoryObserver();

    const observer = new MutationObserver(() => {
        handlePendingClick();

        const skillLabel = document.querySelector('.NavigationBar_active__3R-QS .NavigationBar_label__1uH-y');
        const skill = skillLabel?.textContent.trim();
        if (!skill || !allowedSkills.includes(skill)) return;

        const root = document.querySelector('.GatheringProductionSkillPanel_gatheringProductionSkillPanel__vG4M7');
        if (!root) {
            // V4.1: 如果技能面板根元素不存在,将 lastObservedSkill 置空,确保下次加载时被视为新技能
            lastObservedSkill = null;
            return;
        }

        // V4.1 FIX: 检查技能是否切换
        const isNewSkillLoad = skill !== lastObservedSkill;


        // **新逻辑:为挤奶和伐木注入 Tab 结构**
        if (skill === '挤奶' || skill === '伐木') {
            injectTabStructure(root, skill);
        }

        const tabsContainer = root.querySelector('.MuiTabs-root');
        const isTabbedSkill = !!tabsContainer;

        // 2. 无论是否为 Tabbed 技能,都为物品卡片添加收藏按钮
        document.querySelectorAll('.SkillAction_skillAction__1esCp:not(.SkillAction_opaque__s9Yeq)')
            .forEach(item => addFavoriteButton(item, skill));

        // 3. 只有 Tabbed 技能才执行 Tab 相关的逻辑
        if (isTabbedSkill) {

            ensureFavoritesTab(tabsContainer, skill);

            const tabsFlex = tabsContainer.querySelector('.MuiTabs-flexContainer');
            if (!tabsFlex) return;

            const tabs = tabsFlex.querySelectorAll('button');
            const total = tabs.length;

            // V4.2 FIX: 重新添加事件监听器,确保兼容原生行为并修正自定义 Tab 的状态
            tabs.forEach((tab, i) => {
                // 检查是否已添加监听器
                if (tab.hasAttribute('data-mwi-listener-added')) return;

                tab.setAttribute('data-mwi-listener-added', 'true');
                const isCustomTab = tab.hasAttribute('data-mwi-custom-tab');
                const isFavTab = tab.classList.contains('mwi-fav-tab');

                // 备份原有的点击事件(仅对原生Tab有效)
                const originalClickListener = tab.onclick;
                tab.onclick = null; // 清除原有的 onclick 属性

                tab.addEventListener('click', (e) => {
                    // 获取最新的标签和面板列表
                    const updatedTabs = Array.from(tabsFlex.children);
                    const updatedPanels = root.querySelectorAll('.TabsComponent_tabPanelsContainer__26mzo > .TabPanel_tabPanel__tXMJF');
                    const currentIdx = updatedTabs.indexOf(tab);
                    
                    if (isFavTab) {
                        // 场景 1: 点击的是收藏 Tab
                        e.stopPropagation();
                        switchToTab(skill, currentIdx);
                    } else {
                        // 场景 2: 点击的是原生 Tab(包括挤奶/伐木的原生 Tab)
                        
                        // 1. 触发原生逻辑,让 React 更新其内部状态
                        if (originalClickListener) {
                             try {
                                 originalClickListener.call(tab, e);
                             } catch (err) {
                                 // 忽略原生事件可能的错误
                             }
                        }

                        // 2. 强制更新所有标签状态
                        updatedTabs.forEach((t, idx) => {
                            const isSelected = idx === currentIdx;
                            t.classList.toggle('Mui-selected', isSelected);
                            t.setAttribute('aria-selected', isSelected);
                            t.setAttribute('tabindex', isSelected ? '0' : '-1');
                        });

                        // 3. 强制更新所有面板状态
                        updatedPanels.forEach((p, idx) => {
                            p.classList.toggle('TabPanel_hidden__26UM3', idx !== currentIdx);
                        });

                        // 4. 修复收藏 Tab 状态
                        const favTab = root.querySelector('.mwi-fav-tab');
                        if (favTab) {
                            favTab.classList.remove('Mui-selected');
                            favTab.setAttribute('aria-selected', 'false');
                            favTab.setAttribute('tabindex', '-1');
                        }

                        // 5. 持久化索引
                        setLastTabIndex(skill, currentIdx);
                    }
                });
            });


            // 标签页加载持久化逻辑
            const savedIdx = Math.min(getLastTabIndex(skill), total - 1);
            const currentIdx = Array.from(tabs).findIndex(t => t.classList.contains('Mui-selected'));

            // V4.3 FIX: 核心逻辑:只有在技能刚刚加载时,且目标索引与当前不一致才执行强制切换。
            // 依赖 isNewSkillLoad 确保仅在通过导航栏切换技能时触发。
            if (isNewSkillLoad && currentIdx !== savedIdx) {
                tabRestoreTimeout = setTimeout(() => {
                    // 再次检查当前激活的技能是否一致,以防用户在延迟期间切换了技能
                    const currentSkillCheck = document.querySelector('.NavigationBar_active__3R-QS .NavigationBar_label__1uH-y')?.textContent.trim();
                    if (currentSkillCheck !== skill) return;

                    switchToTab(skill, savedIdx);

                }, 100); // 增加延迟到 100ms 解决可能的竞态条件 (Race Condition)
            }
        }

        // V4.1 核心:在 observer 结束前更新 lastObservedSkill,以检测下一次的技能切换。
        if(skill) {
             lastObservedSkill = skill;
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });
})();