MWIAlchemyCalc

显示炼金收益 milkywayidle 银河奶牛放置

// ==UserScript==
// @name         MWIAlchemyCalc

// @namespace    http://tampermonkey.net/
// @version      20250425.2
// @description  显示炼金收益 milkywayidle 银河奶牛放置

// @author       IOMisaka
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';
    if (!window.mwi) {
        console.error("MWIAlchemyCalc需要安装mooket才能使用");
        return;
    }

    ////////////////code//////////////////
    function hookWS() {
        const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
        const oriGet = dataProperty.get;
        dataProperty.get = hookedGet;
        Object.defineProperty(MessageEvent.prototype, "data", dataProperty);

        function hookedGet() {
            const socket = this.currentTarget;
            if (!(socket instanceof WebSocket)) {
                return oriGet.call(this);
            }
            if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
                return oriGet.call(this);
            }
            const message = oriGet.call(this);
            Object.defineProperty(this, "data", { value: message }); // Anti-loop
            handleMessage(message);
            return message;
        }
    }

    let clientData = null;
    let characterData = null;
    function loadClientData() {
        if (localStorage.getItem("initClientData")) {
            const obj = JSON.parse(localStorage.getItem("initClientData"));
            clientData = obj;
        }
    }

    function handleMessage(message) {
        let obj = JSON.parse(message);
        if (obj) {
            if (obj.type === "init_character_data") {
                characterData = obj;
            } else if (obj.type === "action_type_consumable_slots_updated") {//更新饮料和食物槽数据
                characterData.actionTypeDrinkSlotsMap = obj.actionTypeDrinkSlotsMap;
                characterData.actionTypeFoodSlotsMap = obj.actionTypeFoodSlotsMap;

                handleAlchemyDetailChanged();
            } else if (obj.type === "consumable_buffs_updated") {
                characterData.consumableActionTypeBuffsMap = obj.consumableActionTypeBuffsMap;
                handleAlchemyDetailChanged();
            } else if (obj.type === "community_buffs_updated") {
                characterData.communityActionTypeBuffsMap = obj.communityActionTypeBuffsMap;
                handleAlchemyDetailChanged();
            } else if (obj.type === "equipment_buffs_updated") {//装备buff
                characterData.equipmentActionTypeBuffsMap = obj.equipmentActionTypeBuffsMap;
                characterData.equipmentTaskActionBuffs = obj.equipmentTaskActionBuffs;
                handleAlchemyDetailChanged();
            } else if (obj.type === "house_rooms_updated") {//房屋更新
                characterData.characterHouseRoomMap = obj.characterHouseRoomMap;
                characterData.houseActionTypeBuffsMap = obj.houseActionTypeBuffsMap;
            }
            else if (obj.type === "actions_updated") {
                obj.endCharacterActions?.forEach(
                    action => {
                        if (action.actionHrid.startsWith("/actions/alchemy")) {
                            updateAlchemyAction(action);
                        }
                    }
                );

            }
            else if (obj.type === "action_completed") {//更新技能等级和经验
                if (obj.endCharacterItems) {//道具更新
                    //炼金统计
                    try {
                        if (obj.endCharacterAction.actionHrid.startsWith("/actions/alchemy")) {//炼金统计
                            let outputHashCount = {};
                            let inputHashCount = {};
                            let tempItems = {};
                            obj.endCharacterItems.forEach(
                                item => {
                                    let existItem = tempItems[item.id] || characterData.characterItems.find(x => x.id === item.id);
                                    let delta = (item.count - (existItem?.count || 0));//计数
                                    if (delta < 0) {
                                        inputHashCount[item.hash] = delta;
                                        tempItems[item.id] = item;//替换旧的物品计数
                                    } else if (delta > 0) {
                                        outputHashCount[item.hash] = delta;
                                        tempItems[item.id] = item;//替换旧的物品计数
                                    } else {
                                        console.log("炼金统计出错?不应该为0", item);
                                    }
                                }
                            );
                            let index = [
                                "/actions/alchemy/coinify",
                                "/actions/alchemy/decompose",
                                "/actions/alchemy/transmute"
                            ].findIndex(x => x === obj.endCharacterAction.actionHrid);
                            countAlchemyOutput(inputHashCount, outputHashCount, index);
                        }
                    } catch (e) { }



                    let newIds = obj.endCharacterItems.map(i => i.id);
                    characterData.characterItems = characterData.characterItems.filter(e => !newIds.includes(e.id));//移除存在的物品
                    characterData.characterItems.push(...obj.endCharacterItems);//放入新物品
                }
                if (obj.endCharacterSkills) {
                    for (let newSkill of obj.endCharacterSkills) {
                        let oldSkill = characterData.characterSkills.find(skill => skill.skillHrid === newSkill.skillHrid);

                        oldSkill.level = newSkill.level;
                        oldSkill.experience = newSkill.experience;
                    }
                }
            } else if (obj.type === "items_updated") {
                if (obj.endCharacterItems) {//道具更新
                    let newIds = obj.endCharacterItems.map(i => i.id);
                    characterData.characterItems = characterData.characterItems.filter(e => !newIds.includes(e.id));//移除存在的物品
                    characterData.characterItems.push(...obj.endCharacterItems);//放入新物品
                }
            }
        }
        return message;
    }
    /////////辅助函数,角色动态数据///////////
    // skillHrid = "/skills/alchemy"
    function getSkillLevel(skillHrid, withBuff = false) {
        let skill = characterData.characterSkills.find(skill => skill.skillHrid === skillHrid);
        let level = skill?.level || 0;

        if (withBuff) {//计算buff加成
            level += getBuffValueByType(
                skillHrid.replace("/skills/", "/action_types/"),
                skillHrid.replace("/skills/", "/buff_types/") + "_level"
            );
        }
        return level;
    }

    /// actionTypeHrid = "/action_types/alchemy"
    /// buffTypeHrid = "/buff_types/alchemy_level"
    function getBuffValueByType(actionTypeHrid, buffTypeHrid) {
        let returnValue = 0;
        //社区buff

        for (let buff of characterData.communityActionTypeBuffsMap[actionTypeHrid] || []) {
            if (buff.typeHrid === buffTypeHrid) returnValue += buff.flatBoost;
        }
        //装备buff
        for (let buff of characterData.equipmentActionTypeBuffsMap[actionTypeHrid] || []) {
            if (buff.typeHrid === buffTypeHrid) returnValue += buff.flatBoost;
        }
        //房屋buff
        for (let buff of characterData.houseActionTypeBuffsMap[actionTypeHrid] || []) {
            if (buff.typeHrid === buffTypeHrid) returnValue += buff.flatBoost;
        }
        //茶饮buff
        for (let buff of characterData.consumableActionTypeBuffsMap[actionTypeHrid] || []) {
            if (buff.typeHrid === buffTypeHrid) returnValue += buff.flatBoost;
        }
        return returnValue;
    }
    /**
     * 获取角色ID
     *
     * @returns {string|null} 角色ID,如果不存在则返回null
     */
    function getCharacterId() {
        return characterData?.character.id;
    }
    /**
     * 获取指定物品的数量
     *
     * @param itemHrid 物品的唯一标识
     * @param enhancementLevel 物品强化等级,默认为0
     * @returns 返回指定物品的数量,如果未找到该物品则返回0
     */
    function getItemCount(itemHrid, enhancementLevel = 0) {
        return characterData.characterItems.find(item => item.itemHrid === itemHrid && item.itemLocationHrid === "/item_locations/inventory" && item.enhancementLevel === enhancementLevel)?.count || 0;//背包里面的物品
    }
    //获取饮料状态,传入类型/action_types/brewing,返回列表

    function getDrinkSlots(actionTypeHrid) {
        return characterData.actionTypeDrinkSlotsMap[actionTypeHrid]
    }
    /////////游戏静态数据////////////
    //中英文都有可能
    function getItemHridByShowName(showName) {
        return window.mwi.ensureItemHrid(showName)
    }
    //类似这样的名字blackberry_donut,knights_ingot
    function getItemDataByHridName(hrid_name) {
        return clientData.itemDetailMap["/items/" + hrid_name];
    }
    //类似这样的名字/items/blackberry_donut,/items/knights_ingot
    function getItemDataByHrid(itemHrid) {
        return clientData.itemDetailMap[itemHrid];
    }
    //类似这样的名字Blackberry Donut,Knight's Ingot
    function getItemDataByName(name) {
        return Object.entries(clientData.itemDetailMap).find(([k, v]) => v.name == name);
    }
    function getOpenableItems(itemHrid) {
        let items = [];
        for (let openItem of clientData.openableLootDropMap[itemHrid]) {
            items.push({
                itemHrid: openItem.itemHrid,
                count: (openItem.minCount + openItem.maxCount) / 2 * openItem.dropRate
            });
        }
        return items;
    }
    ////////////观察节点变化/////////////
    function observeNode(nodeSelector, rootSelector, addFunc = null, updateFunc = null, removeFunc = null) {
        const rootNode = document.querySelector(rootSelector);
        if (!rootNode) {
            //console.error(`Root node with selector "${rootSelector}" not found.wait for 1s to try again...`);
            setTimeout(() => observeNode(nodeSelector, rootSelector, addFunc, updateFunc, removeFunc), 1000);
            return;
        }
        console.info(`observing "${rootSelector}"`);

        function delayCall(func, observer, delay = 100) {
            //判断func是function类型
            if (typeof func !== 'function') return;
            // 延迟执行,如果再次调用则在原有基础上继续延时
            func.timeout && clearTimeout(func.timeout);
            func.timeout = setTimeout(() => func(observer), delay);
        }

        const observer = new MutationObserver((mutationsList, observer) => {

            mutationsList.forEach((mutation) => {
                mutation.addedNodes.forEach((addedNode) => {
                    if (addedNode.matches && addedNode.matches(nodeSelector)) {
                        addFunc?.(observer);
                    }
                });

                mutation.removedNodes.forEach((removedNode) => {
                    if (removedNode.matches && removedNode.matches(nodeSelector)) {
                        removeFunc?.(observer);
                    }
                });

                // 处理子节点变化
                if (mutation.type === 'childList') {
                    let node = mutation.target.matches(nodeSelector) ? mutation.target : mutation.target.closest(nodeSelector);
                    if (node) {
                        delayCall(updateFunc, observer); // 延迟 100ms 合并变动处理,避免频繁触发
                    }

                } else if (mutation.type === 'characterData') {
                    // 文本内容变化(如文本节点修改)
                    delayCall(updateFunc, observer);
                }
            });
        });


        const config = {
            childList: true,
            subtree: true,
            characterData: true
        };
        observer.reobserve = function () {
            observer.observe(rootNode, config);
        }//重新观察
        observer.observe(rootNode, config);
        return observer;
    }

    loadClientData();//加载游戏数据
    hookWS();//hook收到角色信息

    //模块逻辑代码
    const MARKET_API_URL = "https://raw.githubusercontent.com/holychikenz/MWIApi/main/milkyapi.json";

    let marketData = JSON.parse(localStorage.getItem("MWIAPI_JSON") || localStorage.getItem("MWITools_marketAPI_json") || "{}");//Use MWITools的API数据
    if (!(marketData?.time > Date.now() / 1000 - 86400)) {//如果本地缓存数据过期,则重新获取
        fetch(MARKET_API_URL).then(res => {
            res.json().then(data => {
                marketData = data;
                //更新本地缓存数据
                localStorage.setItem("MWIAPI_JSON", JSON.stringify(data));//更新本地缓存数据
                console.info("MWIAPI_JSON updated:", new Date(marketData.time * 1000).toLocaleString());
            })
        });
    }


    //返回[买,卖]
    function getPrice(itemHrid, enhancementLevel = 0) {
        return mwi.coreMarket.getItemPrice(itemHrid, enhancementLevel);
    }

    //计算每次的收益
    function calculateProfit(data) {
        let profit = 0;
        let input = 0;
        let output = 0;
        let essence = 0;
        let rare = 0;
        let tea = 0;
        let catalyst = 0;


        for (let item of data.inputItems) {//消耗物品每次必定消耗

            input -= getPrice(item.itemHrid).ask * item.count;//买入材料价格*数量

        }
        for (let item of data.teaUsage) {//茶每次必定消耗
            tea -= getPrice(item.itemHrid).ask * item.count;//买入材料价格*数量
        }

        for (let item of data.outputItems) {//产出物品每次不一定产出,需要计算成功率
            output += getPrice(item.itemHrid).bid * item.count * data.successRate;//卖出产出价格*数量*成功率

        }
        if (data.inputItems[0].itemHrid !== "/items/task_crystal") {//任务水晶有问题,暂时不计算
            for (let item of data.essenceDrops) {//精华和宝箱都要算成功率 -> 不,这两个是按采集的时间出
                essence += getPrice(item.itemHrid).bid * item.count;//采集数据的地方已经算进去了
            }
            for (let item of data.rareDrops) {//宝箱也是按自己的几率出 -> 不
                // getOpenableItems(item.itemHrid).forEach(openItem => {
                //     rare += getPrice(openItem.itemHrid).bid * openItem.count * item.count;//已折算
                // });
                rare += getPrice(item.itemHrid).bid * item.count;
            }
        }
        //催化剂
        for (let item of data.catalystItems) {//催化剂,成功才会用
            catalyst -= getPrice(item.itemHrid).ask * item.count * data.successRate;//买入材料价格*数量
        }

        profit = input + tea + output + essence + rare + catalyst;
        let description = `Last Update:${new Date(marketData.time * 1000).toLocaleString()}\n(效率+${(data.effeciency * 100).toFixed(2)}%)每次收益${profit}=\n\t材料(${input})\n\t茶(${tea})\n\t催化剂(${catalyst})\n\t产出(${output})\n\t精华(${essence})\n\t稀有(${rare})`;
        //console.info(description);
        return [profit, description];//再乘以次数
    }
    function showNumber(num) {
        if (isNaN(num)) return num;
        if (num === 0) return "0";// 单独处理0的情况

        const sign = num > 0 ? '+' : '';
        const absNum = Math.abs(num);

        return absNum >= 1e10 ? `${sign}${(num / 1e9).toFixed(1)}B` :
            absNum >= 1e7 ? `${sign}${(num / 1e6).toFixed(1)}M` :
                absNum >= 1e4 ? `${sign}${Math.floor(num / 1e3)}K` :
                    `${sign}${Math.floor(num)}`;
    }
    function parseNumber(str) {
        return parseInt(str.replaceAll("/", "").replaceAll(",", "").replaceAll(" ", ""));
    }
    let predictPerDay = {};
    function handleAlchemyDetailChanged(observer) {
        let inputItems = [];
        let outputItems = [];
        let essenceDrops = [];
        let rareDrops = [];
        let teaUsage = [];
        let catalystItems = [];

        let costNodes = document.querySelector(".AlchemyPanel_skillActionDetailContainer__o9SsW .SkillActionDetail_itemRequirements__3SPnA");
        if (!costNodes) return;//没有炼金详情就不处理

        let costs = Array.from(costNodes.children);
        //每三个元素取textContent拼接成一个字符串,用空格和/分割
        for (let i = 0; i < costs.length; i += 3) {

            let need = parseNumber(costs[i + 1].textContent);
            let nameArr = costs[i + 2].textContent.split("+");
            let itemHrid = getItemHridByShowName(nameArr[0]);
            let enhancementLevel = nameArr.length > 1 ? parseNumber(nameArr[1]) : 0;

            inputItems.push({ itemHrid: itemHrid, enhancementLevel: enhancementLevel, count: need });
        }

        //炼金输出
        for (let line of document.querySelectorAll(".SkillActionDetail_alchemyOutput__6-92q .SkillActionDetail_drop__26KBZ")) {
            let count = parseFloat(line.children[0].textContent.replaceAll(",", ""));
            let itemName = line.children[1].textContent;
            let rate = line.children[2].textContent ? parseFloat(line.children[2].textContent.substring(1, line.children[2].textContent.length - 1) / 100.0) : 1;//默认1
            outputItems.push({ itemHrid: getItemHridByShowName(itemName), count: count * rate });
        }
        //精华输出
        for (let line of document.querySelectorAll(".SkillActionDetail_essenceDrops__2skiB .SkillActionDetail_drop__26KBZ")) {
            let count = parseFloat(line.children[0].textContent);
            let itemName = line.children[1].textContent;
            let rate = line.children[2].textContent ? parseFloat(line.children[2].textContent.substring(1, line.children[2].textContent.length - 1) / 100.0) : 1;//默认1
            essenceDrops.push({ itemHrid: getItemHridByShowName(itemName), count: count * rate });
        }
        //稀有输出
        for (let line of document.querySelectorAll(".SkillActionDetail_rareDrops__3OTzu .SkillActionDetail_drop__26KBZ")) {
            let count = parseFloat(line.children[0].textContent);
            let itemName = line.children[1].textContent;
            let rate = line.children[2].textContent ? parseFloat(line.children[2].textContent.substring(1, line.children[2].textContent.length - 1) / 100.0) : 1;//默认1
            rareDrops.push({ itemHrid: getItemHridByShowName(itemName), count: count * rate });
        }
        //成功率
        let successRateStr = document.querySelector(".SkillActionDetail_successRate__2jPEP .SkillActionDetail_value__dQjYH").textContent;
        let successRate = parseFloat(successRateStr.substring(0, successRateStr.length - 1)) / 100.0;

        //消耗时间
        let costTimeStr = document.querySelector(".SkillActionDetail_timeCost__1jb2x .SkillActionDetail_value__dQjYH").textContent;
        let costSeconds = parseFloat(costTimeStr.substring(0, costTimeStr.length - 1));//秒,有分再改



        //催化剂
        let catalystItem = document.querySelector(".SkillActionDetail_catalystItemInput__2ERjq .Icon_icon__2LtL_") || document.querySelector(".SkillActionDetail_catalystItemInputContainer__5zmou .Item_iconContainer__5z7j4 .Icon_icon__2LtL_");//过程中是另一个框
        if (catalystItem) {
            catalystItems = [{ itemHrid: getItemHridByShowName(catalystItem.getAttribute("aria-label")), count: 1 }];
        }

        //计算效率
        let effeciency = getBuffValueByType("/action_types/alchemy", "/buff_types/efficiency");
        let skillLevel = getSkillLevel("/skills/alchemy", true);
        let mainItem = getItemDataByHrid(inputItems[0].itemHrid);
        if (mainItem.itemLevel) {
            effeciency += Math.max(0, skillLevel - mainItem.itemLevel) / 100;//等级加成
        }

        //costSeconds = costSeconds * (1 - effeciency);//效率,相当于减少每次的时间
        costSeconds = costSeconds / (1 + effeciency);
        //茶饮,茶饮的消耗就减少了
        let teas = getDrinkSlots("/action_types/alchemy");//炼金茶配置
        for (let tea of teas) {
            if (tea) {//有可能空位
                teaUsage.push({ itemHrid: tea.itemHrid, count: costSeconds / 300 });//300秒消耗一个茶
            }
        }
        console.info("效率", effeciency);


        //返回结果
        let ret = {
            inputItems: inputItems,
            outputItems: outputItems,
            essenceDrops: essenceDrops,
            rareDrops: rareDrops,
            successRate: successRate,
            costTime: costSeconds,
            teaUsage: teaUsage,
            catalystItems: catalystItems,
            effeciency: effeciency,
        }

        //次数,收益
        let result = calculateProfit(ret);
        let profit = result[0];
        let desc = result[1];

        let timesPerHour = 3600 / costSeconds;//加了效率相当于增加了次数
        let profitPerHour = profit * timesPerHour;

        let timesPerDay = 24 * timesPerHour;
        let profitPerDay = profit * timesPerDay;

        const buttons = document.querySelectorAll(".AlchemyPanel_tabsComponentContainer__1f7FY .MuiButtonBase-root.MuiTab-root.MuiTab-textColorPrimary.css-1q2h7u5");
        const selectedIndex = Array.from(buttons).findIndex(button =>
            button.classList.contains('Mui-selected')
        );
        predictPerDay[selectedIndex] = profitPerDay;//记录第几个对应的每日收益

        observer?.disconnect();//断开观察

        //显示位置
        let showParent = document.querySelector(".SkillActionDetail_notes__2je2F");
        let label = showParent.querySelector("#alchemoo");
        if (!label) {
            label = document.createElement("div");
            label.id = "alchemoo";
            showParent.appendChild(label);
        }

        let color = "white";
        if (profitPerHour > 0) {
            color = "lime";
        } else if (profitPerHour < 0) {
            color = "red";
        }
        label.innerHTML = `
        <div id="alchemoo" style="color: ${color};">
            <span title="${desc}">预估收益ℹ️:</span><br/>
            <span>🪙${showNumber(profit)}/次</span><br/>
            <span title="${showNumber(timesPerHour)}次">🪙${showNumber(profitPerHour)}/时</span><br/>
            <span title="${showNumber(timesPerDay)}次">🪙${showNumber(profitPerDay)}/天</span>
            </div>`;

        //console.log(ret);
        observer?.reobserve();
    }

    observeNode(".SkillActionDetail_alchemyComponent__1J55d", "body", handleAlchemyDetailChanged, handleAlchemyDetailChanged);

    let currentInput = {};
    let currentOutput = {};
    let alchemyStartTime = Date.now();
    let lastAction = null;
    let needClear = false;
    let alchemyIndex = 0;
    //统计功能
    function countAlchemyOutput(inputHashCount, outputHashCount, index) {
        alchemyIndex = index;
        if (needClear) {
            currentOutput = {};
            currentInput = {};
            alchemyStartTime = Date.now();//重置开始时间
            needClear = false;
        }
        for (let itemHash in inputHashCount) {
            currentInput[itemHash] = (currentInput[itemHash] || 0) + inputHashCount[itemHash];
        }
        for (let itemHash in outputHashCount) {
            currentOutput[itemHash] = (currentOutput[itemHash] || 0) + outputHashCount[itemHash];
        }
        showOutput();
    }

    function updateAlchemyAction(action) {
        if ((!lastAction) || (lastAction.id != action.id)) {//新动作,重置统计信息
            lastAction = action;
            needClear = true;
            showOutput();//显示统计信息
            return;
        }
        showOutput();
    }
    function calcChestPrice(itemHrid) {
        let total = 0;
        getOpenableItems(itemHrid).forEach(openItem => {
            total += getPrice(openItem.itemHrid).bid * openItem.count;
        });
        return total;
    }
    function calcPrice(items) {
        let total = 0;
        for (let item of items) {

            if (item.itemHrid === "/items/task_crystal") {//任务水晶有问题,暂时不计算
            }
            else if (getItemDataByHrid(item.itemHrid)?.categoryHrid === "/item_categories/loot") {
                total += calcChestPrice(item.itemHrid) * item.count;
            } else {
                total += getPrice(item.itemHrid, item.enhancementLevel ?? 0).ask * item.count;//买入材料价格*数量
            }

        }
        return total;
    }
    function itemHashToItem(itemHash) {
        let item = {};
        let arr = itemHash.split("::");
        item.itemHrid = arr[2];
        item.enhancementLevel = arr[3];
        return item;
    }
    function getItemNameByHrid(itemHrid) {
        return mwi.isZh ?
            mwi.lang.zh.translation.itemNames[itemHrid] : mwi.lang.en.translation.itemNames[itemHrid];
    }
    function secondsToHms(seconds) {
        seconds = Number(seconds);
        const h = Math.floor(seconds / 3600);
        const m = Math.floor((seconds % 3600) / 60);
        const s = Math.floor(seconds % 60);

        return [
            h.toString().padStart(2, '0'),
            m.toString().padStart(2, '0'),
            s.toString().padStart(2, '0')
        ].join(':');
    }
    function showOutput() {
        let alchemyContainer = document.querySelector(".SkillActionDetail_alchemyComponent__1J55d");
        if (!alchemyContainer) return;

        if (!document.querySelector("#alchemoo_result")) {
            let outputContainer = document.createElement("div");
            outputContainer.id = "alchemoo_result";
            outputContainer.style.fontSize = "13px";
            outputContainer.style.lineHeight = "16px";
            outputContainer.style.maxWidth = "220px";
            outputContainer.innerHTML = `
            <div id="alchemoo_title" style="font-weight: bold; margin-bottom: 10px; text-align: center; color: var(--color-space-300);">炼金结果</div>
            <div id="alchemoo_cost" style="display: flex; flex-wrap: wrap; gap: 4px;"></div>
            <div id="alchemoo_rate"></div>
            <div id="alchemoo_output" style="display: flex; flex-wrap: wrap; gap: 4px;"></div>
            <div id="alchemoo_essence"></div>
            <div id="alchemoo_rare"></div>
            <div id="alchemoo_exp"></div>
            <div id="alchemoo_time"></div>
            <div id="alchemoo_total" style="font-weight:bold;font-size:16px;border:1px solid var(--color-space-300);border-radius:4px;padding:1px 5px;display: flex; flex-direction: column; align-items: flex-start; gap: 4px;"></div>
            `;
            outputContainer.style.flex = "0 0 auto";
            alchemyContainer.appendChild(outputContainer);
        }
        "💰"

        let cost = calcPrice(Object.entries(currentInput).map(
            ([itemHash, count]) => {
                let arr = itemHash.split("::");
                return { "itemHrid": arr[2], "enhancementLevel": parseInt(arr[3]), "count": count }
            })
        );
        let gain = calcPrice(Object.entries(currentOutput).map(
            ([itemHash, count]) => {
                let arr = itemHash.split("::");
                return { "itemHrid": arr[2], "enhancementLevel": parseInt(arr[3]), "count": count }
            })
        );
        let total = cost + gain;

        let text = "";
        //消耗
        Object.entries(currentInput).forEach(([itemHash, count]) => {
            let item = itemHashToItem(itemHash);
            let price = getPrice(item.itemHrid);
            text += `
            <div title="直买价:${price.ask}" style="display: inline-flex;border:1px solid var(--color-space-300);border-radius:4px;padding:1px 5px;">
            <svg width="14px" height="14px" style="display:inline-block"><use href="/static/media/items_sprite.6d12eb9d.svg#${item.itemHrid.replace("/items/", "")}"></use></svg>
            <span style="display:inline-block">${getItemNameByHrid(item.itemHrid)}</span>
            <span style="color:red;display:inline-block;font-size:14px;">${showNumber(count).replace("-", "*")}</span>
            </div>
            `;
        });
        text += `<div style="display: inline-block;border:1px solid var(--color-space-300);border-radius:4px;padding:1px 5px;"><span style="color:red;font-size:16px;">${showNumber(cost)}</span></div>`;
        document.querySelector("#alchemoo_cost").innerHTML = text;

        document.querySelector("#alchemoo_rate").innerHTML = `<br/>`;//成功率

        text = "";
        Object.entries(currentOutput).forEach(([itemHash, count]) => {
            let item = itemHashToItem(itemHash);
            let price = getPrice(item.itemHrid);
            text += `
            <div title="直卖价:${price.bid}" style="display: inline-flex;border:1px solid var(--color-space-300);border-radius:4px;padding:1px 5px;">
            <svg width="14px" height="14px" style="display:inline-block"><use href="/static/media/items_sprite.6d12eb9d.svg#${item.itemHrid.replace("/items/", "")}"></use></svg>
            <span style="display:inline-block">${getItemNameByHrid(item.itemHrid)}</span>
            <span style="color:lime;display:inline-block;font-size:14px;">${showNumber(count).replace("+", "*")}</span>
            </div>
            `;
        });
        text += `<div style="display: inline-block;border:1px solid var(--color-space-300);border-radius:4px;padding:1px 5px;"><span style="color:lime;font-size:16px;">${showNumber(gain)}</span></div>`;
        document.querySelector("#alchemoo_output").innerHTML = text;//产出

        //document.querySelector("#alchemoo_essence").innerHTML = `<br/>`;//精华
        //document.querySelector("#alchemoo_rare").innerHTML = `<br/>`;//稀有
        document.querySelector("#alchemoo_exp").innerHTML = `<br/>`;//经验
        let time = (Date.now() - alchemyStartTime) / 1000;
        //document.querySelector("#alchemoo_time").innerHTML = `<span>耗时:${secondsToHms(time)}</span>`;//时间
        let perDay = (86400 / time) * total;

        let profitPerDay = predictPerDay[alchemyIndex] || 0;
        document.querySelector("#alchemoo_total").innerHTML =
            `
        <span>耗时:${secondsToHms(time)}</span>
        <div>累计收益:<span style="color:${total > 0 ? "lime" : "red"}">${showNumber(total)}</span></div>
        <div>每日收益:<span style="color:${perDay > profitPerDay ? "lime" : "red"}">${showNumber(total * (86400 / time)).replace("+", perDay > profitPerDay ? "↑" : "↓")}</span></div>
        `;//总收益
    }
    //mwi.hookMessage("action_completed", countAlchemyOutput);
    //mwi.hookMessage("action_updated", updateAlchemyAction)
})();