[MWI] Realtime Import Of Battle Simulation

Battle simulation imports the realtime configuration of the current character.

// ==UserScript==
// @name         [MWI] Realtime Import Of Battle Simulation
// @name:zh-CN   [银河奶牛]战斗模拟实时导入
// @namespace    http://tampermonkey.net/
// @version      0.1.5
// @description  Battle simulation imports the realtime configuration of the current character.
// @description:zh-CN  战斗模拟辅助工具,实时监听角色配置变化,导入当前角色实时配置
// @icon         https://www.milkywayidle.com/favicon.svg
// @author       Yannis
// @license      CC-BY-NC-SA-4.0
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @match        https://*/MWICombatSimulatorTest/dist/*
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

/**
 * 感谢 'MWITool' 为本脚本提供的技术参考,本脚本部分代码来源于 MWITool,请勿删除本版权声明
 * 本脚本若有任何问题,欢迎随时与开发者联系与反馈,感谢使用
 * Thanks 'MWITool' for the technical reference provided for this script.
 * Some of the code in this script is sourced from MWITool.
 * Please do not delete this copyright notice.
 *
 * https://greasyfork.runtimutd.eu.org/en/scripts/494467-mwitools
 */

(function () {
    'use strict';

    const debug = console.log.bind(null, '%c[BatSync]%c', 'color:cyan', 'color:black');
    const info = console.log.bind(null, '%c[BatSync]%c', 'color:green', 'color:black');
    const error = console.log.bind(null, '%c[BatSync]%c', 'color:red', 'color:black');

    // 语言设定
    const isZHInGameSetting = localStorage.getItem("i18nextLng")?.toLowerCase()?.startsWith("zh");
    let isZH = isZHInGameSetting;

    let playerId;
    let clientData = {};

    // 监听WebSocket
    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

            try {
                handleMessage(message);
            } catch (e) {
                error(`处理消息协议时出错: ${e}`);
                console.log(e.stack);
            }
            return message;
        }
    }

    // 获取客户端初始化数据
    function getInitClientData() {
        return JSON.parse(GM_getValue("init_client_data", ""));
    }

    // 获取当前角色数据
    function getCurrentPlayerData() {
        let playersDataStr = GM_getValue("init_character_data", "");
        if (playersDataStr) {
            let playerData = JSON.parse(playersDataStr);
            return getPlayerData(playerData.character.id);
        } else {
            return;
        }
    }

    // 获取角色数据
    function getPlayerData(id) {
        let playersDataStr = GM_getValue("mwi_players_data", null) || JSON.stringify(new Array());
        let playersData = JSON.parse(playersDataStr);
        const pIndex = playersData.findIndex(obj => obj.character.id === id);
        if (pIndex !== -1) {
            return playersData[pIndex];
        } else {
            return;
        }
    }

    // 保存角色数据
    function saveCharacterData(obj) {
        let playersDataStr = GM_getValue("mwi_players_data", null) || JSON.stringify(new Array());
        let playersData = JSON.parse(playersDataStr);
        playersData = playersData.filter(e => e.character.id !== obj.character.id);
        playersData.unshift(obj);
        if (playersData.length > 20) {
            playersData.pop();
        }
        GM_setValue("mwi_players_data", JSON.stringify(playersData));
    }

    // 消息处理
    function handleMessage(message) {
        let obj = JSON.parse(message);
        if (!obj) {
            return;
        }
        switch (obj.type) {
            case 'pong': {
                // ping-pong
                break;
            }
            case 'active_player_count_updated': {
                // 活跃人数更新
                break;
            }
            case 'init_client_data': {
                // 客户端数据
                GM_setValue("init_client_data", message);
                clientData.actionDetailMap = obj.actionDetailMap;
                clientData.levelExperienceTable = obj.levelExperienceTable;
                clientData.itemDetailMap = obj.itemDetailMap;
                clientData.actionCategoryDetailMap = obj.actionCategoryDetailMap;
                clientData.abilityDetailMap = obj.abilityDetailMap;
                break;
            }
            case 'init_character_data': {
                // 初始化信息
                GM_setValue("init_character_data", message);
                playerId = obj.character.id;
                obj.battleObj = buildBattleObjFromPlayer(obj, true);
                saveCharacterData(obj);
                break;
            }
            case 'profile_shared': {
                // 角色详情
                let player = getPlayerData(obj.profile.characterSkills[0].characterID)
                let battleObj = buildBattleObjFromProfileShared(player, obj);
                if (!player) {
                    // 不是本角色
                    player = {}
                    player.character = {}
                    player.character.id = battleObj.character.id
                    player.character.name = battleObj.character.name
                }
                player.battleObj = battleObj;
                saveCharacterData(player);
                break;
            }
            case 'new_battle': {
                // 战斗更新
                for (const battlePlayer of obj.players) {
                    let player = getPlayerData(battlePlayer.character.id);
                    let battleObj = buildBattleObjFromNewBattle(player, battlePlayer);
                    if (!player) {
                        // 不是本角色
                        player = {}
                        player.character = {}
                        player.character.id = battleObj.character.id
                        player.character.name = battleObj.character.name
                    }
                    player.battleObj = battleObj;
                    saveCharacterData(player);
                }
                break;
            }
            case 'items_updated': {
                // 物品更新
                let player = getPlayerData(playerId);
                let update = false;
                if (obj.endCharacterItems) {
                    for (const item of Object.values(obj.endCharacterItems)) {
                        if (item.itemLocationHrid !== "/item_locations/inventory" && item.count > 0) {
                            // 装备更新
                            let equipment = player.battleObj.player.equipment;
                            equipment = equipment.filter(e => e.itemLocationHrid !== item.itemLocationHrid);
                            equipment.push({
                                itemLocationHrid: item.itemLocationHrid,
                                itemHrid: item.itemHrid,
                                enhancementLevel: item.enhancementLevel,
                            })
                            player.battleObj.player.equipment = equipment;
                            update = true;
                        }
                    }
                }
                if (update) {
                    saveCharacterData(player);
                }
                break;
            }
            case 'action_type_consumable_slots_updated': {
                // 消耗栏更新
                let player = getPlayerData(playerId);
                player.actionTypeDrinkSlotsMap = obj.actionTypeDrinkSlotsMap;
                player.actionTypeFoodSlotsMap = obj.actionTypeFoodSlotsMap;
                player.battleObj = buildBattleObjFromPlayer(player, false);
                saveCharacterData(player);
                break;
            }
            case 'abilities_updated': {
                // 技能更新
                let player = getPlayerData(playerId);
                let equippedAbilities = JSON.parse(JSON.stringify(player.combatUnit.combatAbilities));
                for (let i = equippedAbilities.length; i < 5; i++) {
                    equippedAbilities.push({})
                }
                if (obj.endCharacterAbilities) {
                    for (const ability of obj.endCharacterAbilities) {
                        const aIndex = equippedAbilities.findIndex(obj => obj.abilityHrid === ability.abilityHrid);
                        if (aIndex >= 0) {
                            equippedAbilities[aIndex] = {}
                        }
                        if (ability.slotNumber > 0) {
                            equippedAbilities.splice(ability.slotNumber - 1, 0, {
                                abilityHrid: ability.abilityHrid,
                                level: ability.level,
                                experience: ability.experience,
                                availableTime: ability.updatedAt
                            })
                        }
                    }
                }
                player.combatUnit.combatAbilities = equippedAbilities.filter(e => e.abilityHrid && e.abilityHrid.length > 0);
                player.battleObj = buildBattleObjFromPlayer(player, false);
                saveCharacterData(player);
                break;
            }
            case 'combat_triggers_updated': {
                let player = getPlayerData(playerId);
                if (obj.combatTriggerTypeHrid === '/combat_trigger_types/ability') {
                    // 技能栏 Trigger 更新
                    player.abilityCombatTriggersMap[obj.abilityHrid] = obj.combatTriggers;
                } else if (obj.combatTriggerTypeHrid === '/combat_trigger_types/consumable') {
                    // 消耗栏 Trigger 更新
                    player.consumableCombatTriggersMap[obj.itemHrid] = obj.combatTriggers;
                } else {
                    break;
                }
                player.battleObj = buildBattleObjFromPlayer(player, false);
                saveCharacterData(player);
                break;
            }
            case 'all_combat_triggers_updated': {
                // 所有 Triggers 更新
                let player = getPlayerData(playerId);
                player.abilityCombatTriggersMap = { ...player.abilityCombatTriggersMap, ...obj.abilityCombatTriggersMap };
                player.consumableCombatTriggersMap = { ...player.consumableCombatTriggersMap, ...obj.consumableCombatTriggersMap };
                player.battleObj = buildBattleObjFromPlayer(player, false);
                saveCharacterData(player);
                break;
            }
            case 'party_updated': {
                // 队伍更新
                let player = getPlayerData(playerId);
                player.partyInfo = obj.partyInfo;
                saveCharacterData(player);
                break;
            }
            case 'chat_message_received': {
                // 聊天消息
                break;
            }
            case 'action_completed': {
                // 行动完成
                break;
            }
            default: {
                // debug(obj);
            }
        }
    }

    // 添加实时导入按钮
    function addImportButtonForMWICombatSimulate() {
        const checkElem = () => {
            const btnEquipSets = document.querySelector(`button#buttonEquipmentSets`);
            if (btnEquipSets) {
                clearInterval(timer);

                let divRow = document.createElement("div");
                divRow.className = "row";
                btnEquipSets.parentElement.parentElement.prepend(divRow);

                // 导入按钮
                let div1 = document.createElement("div");
                div1.className = "col-md-auto mb-3 pt-1";
                divRow.append(div1);
                let button = document.createElement("button");
                div1.append(button);
                button.textContent = isZH ? "实时导入数据" : "Real-time Import";
                button.className = "btn btn-warning";
                button.onclick = function () {
                    const btnGetPrice = document.querySelector(`button#buttonGetPrices`);
                    if (btnGetPrice) {
                        btnGetPrice.click();
                    }
                    importDataForMWICombatSimulate(button);
                    return false;
                };
            }
        };
        let timer = setInterval(checkElem, 200);
    }

    // 导入数据
    async function importDataForMWICombatSimulate(button) {
        clientData = getInitClientData();
        let player = getCurrentPlayerData();

        const BLANK_PLAYER_JSON_STR = `{\"player\":{\"attackLevel\":1,\"magicLevel\":1,\"powerLevel\":1,\"rangedLevel\":1,\"defenseLevel\":1,\"staminaLevel\":1,\"intelligenceLevel\":1,\"equipment\":[]},\"food\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"drinks\":{\"/action_types/combat\":[{\"itemHrid\":\"\"},{\"itemHrid\":\"\"},{\"itemHrid\":\"\"}]},\"abilities\":[{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"},{\"abilityHrid\":\"\",\"level\":\"1\"}],\"triggerMap\":{},\"zone\":\"/actions/combat/fly\",\"simulationTime\":\"100\",\"houseRooms\":{\"/house_rooms/dairy_barn\":0,\"/house_rooms/garden\":0,\"/house_rooms/log_shed\":0,\"/house_rooms/forge\":0,\"/house_rooms/workshop\":0,\"/house_rooms/sewing_parlor\":0,\"/house_rooms/kitchen\":0,\"/house_rooms/brewery\":0,\"/house_rooms/laboratory\":0,\"/house_rooms/observatory\":0,\"/house_rooms/dining_room\":0,\"/house_rooms/library\":0,\"/house_rooms/dojo\":0,\"/house_rooms/gym\":0,\"/house_rooms/armory\":0,\"/house_rooms/archery_range\":0,\"/house_rooms/mystical_study\":0}}`;

        const playerNames = {};
        const imported = {};
        const battleData = {};
        let isParty = false;
        let zone = "/actions/combat/fly";
        let isZoneDungeon = false;

        if (!player?.partyInfo?.partySlotMap) {
            // 个人
            playerNames[0] = player.character.name;
            imported[0] = true;
            battleData[0] = player.battleObj;
            // Zone
            for (const action of player.characterActions) {
                if (action && action.actionHrid.includes("/actions/combat/")) {
                    zone = action.actionHrid;
                    isZoneDungeon = clientData.actionDetailMap[action.actionHrid]?.combatZoneInfo?.isDungeon;
                    break;
                }
            }
        } else {
            // 队伍
            isParty = true;
            let i = 0;
            for (const member of Object.values(player.partyInfo.partySlotMap)) {
                i++;
                if (member.characterID) {
                    if (member.characterID === player.character.id) {
                        playerNames[i] = player.character.name;
                        imported[i] = true;
                        battleData[i] = JSON.stringify(player.battleObj);
                        continue;
                    } else {
                        let memberData = getPlayerData(member.characterID);
                        if (memberData && memberData.battleObj?.valid) {
                            playerNames[i] = memberData.character.name;
                            imported[i] = true;
                            battleData[i] = JSON.stringify(memberData.battleObj);
                            continue;
                        } else {
                            playerNames[i] = isZH ? "需要点开个人资料" : "Open profile in game";
                            imported[i] = true;
                            battleData[i] = BLANK_PLAYER_JSON_STR;
                        }
                    }
                }
            }
            // Zone
            zone = player.partyInfo?.party?.actionHrid;
            isZoneDungeon = clientData.actionDetailMap[zone]?.combatZoneInfo?.isDungeon;
        }

        // Select zone or dungeon
        if (zone) {
            if (isZoneDungeon) {
                document.querySelector(`input#simDungeonToggle`).checked = true;
                document.querySelector(`input#simDungeonToggle`).dispatchEvent(new Event("change"));
                const selectDungeon = document.querySelector(`select#selectDungeon`);
                for (let i = 0; i < selectDungeon.options.length; i++) {
                    if (selectDungeon.options[i].value === zone) {
                        selectDungeon.options[i].selected = true;
                        break;
                    }
                }
            } else {
                document.querySelector(`input#simDungeonToggle`).checked = false;
                document.querySelector(`input#simDungeonToggle`).dispatchEvent(new Event("change"));
                const selectZone = document.querySelector(`select#selectZone`);
                for (let i = 0; i < selectZone.options.length; i++) {
                    if (selectZone.options[i].value === zone) {
                        selectZone.options[i].selected = true;
                        break;
                    }
                }
            }
        }

        for (let i = 1; i <= 5; i++) {
            if (!battleData[i]) {
                battleData[i] = BLANK_PLAYER_JSON_STR;
                playerNames[i] = `Player ${i}`;
                imported[i] = false;
            }
            document.querySelector(`a#player${i}-tab`).textContent = playerNames[i];
            if (document.querySelector(`input#player${i}.form-check-input.player-checkbox`)) {
                document.querySelector(`input#player${i}.form-check-input.player-checkbox`).checked = imported[i];
                document.querySelector(`input#player${i}.form-check-input.player-checkbox`).dispatchEvent(new Event("change"));
            }
        }

        document.querySelector(`a#group-combat-tab`).click();
        const editImport = document.querySelector(`input#inputSetGroupCombatAll`);
        editImport.value = JSON.stringify(battleData);
        document.querySelector(`button#buttonImportSet`).click();

        // 模拟时长
        document.querySelector(`input#inputSimulationTime`).value = 24;

        button.textContent = isZH ? "成功导入数据" : "Imported Successful";
        button.className = "btn btn-success";
        setTimeout(() => {
            button.textContent = isZH ? "实时导入数据" : "Real-time Import";
            button.className = "btn btn-warning";
        }, 1000);

        if (!isParty) {
            setTimeout(() => {
                document.querySelector(`button#buttonStartSimulation`).click();
            }, 500);
        }
    }

    // 监听模拟结果
    async function observeResultsForMWICombatSimulate() {
        let resultDiv = document.querySelector(`div.row`)?.querySelectorAll(`div.col-md-5`)?.[2]?.querySelector(`div.row > div.col-md-5`);
        while (!resultDiv) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            resultDiv = document.querySelector(`div.row`)?.querySelectorAll(`div.col-md-5`)?.[2]?.querySelector(`div.row > div.col-md-5`);
        }

        const deathDiv = document.querySelector(`div#simulationResultPlayerDeaths`);
        const expDiv = document.querySelector(`div#simulationResultExperienceGain`);
        const consumeDiv = document.querySelector(`div#simulationResultConsumablesUsed`);
        deathDiv.style.backgroundColor = "#FFEAE9";
        deathDiv.style.color = "black";
        expDiv.style.backgroundColor = "#CDFFDD";
        expDiv.style.color = "black";
        consumeDiv.style.backgroundColor = "#F0F8FF";
        consumeDiv.style.color = "black";

        let div = document.createElement("div");
        div.id = "tillLevel";
        div.style.backgroundColor = "#FFFFE0";
        div.style.color = "black";
        div.textContent = "";
        resultDiv.append(div);
    }

    // 构建战斗模拟信息(InitData)
    function buildBattleObjFromPlayer(obj, init) {
        let battleObj = init ? {} : obj.battleObj;
        // Base
        battleObj.character = {}
        battleObj.character.id = obj.character.id;
        battleObj.character.name = obj.character.name;
        battleObj.character.gameMode = obj.character.gameMode;
        battleObj.timestamp = Date.now();
        battleObj.valid = true;
        if (init) {
            // Levels
            battleObj.player = {}
            for (const skill of obj.characterSkills) {
                if (skill.skillHrid.includes("stamina")) {
                    battleObj.player.staminaLevel = skill.level;
                } else if (skill.skillHrid.includes("intelligence")) {
                    battleObj.player.intelligenceLevel = skill.level;
                } else if (skill.skillHrid.includes("attack")) {
                    battleObj.player.attackLevel = skill.level;
                } else if (skill.skillHrid.includes("power")) {
                    battleObj.player.powerLevel = skill.level;
                } else if (skill.skillHrid.includes("defense")) {
                    battleObj.player.defenseLevel = skill.level;
                } else if (skill.skillHrid.includes("ranged")) {
                    battleObj.player.rangedLevel = skill.level;
                } else if (skill.skillHrid.includes("magic")) {
                    battleObj.player.magicLevel = skill.level;
                }
            }
            // Equipments
            battleObj.player.equipment = [];
            if (obj.characterItems) {
                for (const item of obj.characterItems) {
                    if (!item.itemLocationHrid.includes("/item_locations/inventory")) {
                        battleObj.player.equipment.push({
                            itemLocationHrid: item.itemLocationHrid,
                            itemHrid: item.itemHrid,
                            enhancementLevel: item.enhancementLevel,
                        });
                    }
                }
            }
        }
        // Food
        battleObj.food = {}
        battleObj.food["/action_types/combat"] = [];
        if (obj.actionTypeFoodSlotsMap["/action_types/combat"]) {
            for (const food of obj.actionTypeFoodSlotsMap["/action_types/combat"]) {
                if (food) {
                    battleObj.food["/action_types/combat"].push({
                        itemHrid: food.itemHrid,
                    });
                } else {
                    battleObj.food["/action_types/combat"].push({
                        itemHrid: "",
                    });
                }
            }
        }
        // Drinks
        battleObj.drinks = {}
        battleObj.drinks["/action_types/combat"] = [];
        if (obj.actionTypeDrinkSlotsMap["/action_types/combat"]) {
            for (const drink of obj.actionTypeDrinkSlotsMap["/action_types/combat"]) {
                if (drink) {
                    battleObj.drinks["/action_types/combat"].push({
                        itemHrid: drink.itemHrid,
                    });
                } else {
                    battleObj.drinks["/action_types/combat"].push({
                        itemHrid: "",
                    });
                }
            }
        }
        // Abilities
        battleObj.abilities = [];
        for (let i = 0; i < 5; i++) {
            battleObj.abilities.push({
                abilityHrid: "",
                level: "1",
            })
        }
        if (obj.combatUnit.combatAbilities) {
            let index = 1;
            for (const ability of obj.combatUnit.combatAbilities) {
                if (ability && clientData.abilityDetailMap[ability.abilityHrid].isSpecialAbility) {
                    battleObj.abilities[0] = {
                        abilityHrid: ability.abilityHrid,
                        level: ability.level,
                        experience: ability.experience,
                        availableTime: ability.updatedAt
                    };
                } else if (ability) {
                    battleObj.abilities[index++] = {
                        abilityHrid: ability.abilityHrid,
                        level: ability.level,
                        experience: ability.experience,
                        availableTime: ability.updatedAt
                    };
                }
            }
        }
        // TriggerMap
        battleObj.triggerMap = { ...obj.abilityCombatTriggersMap, ...obj.consumableCombatTriggersMap };
        // HouseRooms
        battleObj.houseRooms = {};
        if (obj.characterHouseRoomMap) {
            for (const house of Object.values(obj.characterHouseRoomMap)) {
                battleObj.houseRooms[house.houseRoomHrid] = house.level;
            }
        }
        return battleObj;
    }

    // 构建战斗模拟信息(ProfileShared)
    function buildBattleObjFromProfileShared(player, obj) {
        let battleObj = {};
        // Base
        battleObj.character = {}
        battleObj.character.id = player ? player.character.id : obj.profile.characterSkills[0].characterID;
        battleObj.character.name = obj.profile.sharableCharacter.name;
        battleObj.character.gameMode = obj.profile.sharableCharacter.gameMode;
        battleObj.timestamp = Date.now();
        battleObj.valid = true;
        // Levels
        battleObj.player = {}
        for (const skill of obj.profile.characterSkills) {
            if (skill.skillHrid.includes("stamina")) {
                battleObj.player.staminaLevel = skill.level;
            } else if (skill.skillHrid.includes("intelligence")) {
                battleObj.player.intelligenceLevel = skill.level;
            } else if (skill.skillHrid.includes("attack")) {
                battleObj.player.attackLevel = skill.level;
            } else if (skill.skillHrid.includes("power")) {
                battleObj.player.powerLevel = skill.level;
            } else if (skill.skillHrid.includes("defense")) {
                battleObj.player.defenseLevel = skill.level;
            } else if (skill.skillHrid.includes("ranged")) {
                battleObj.player.rangedLevel = skill.level;
            } else if (skill.skillHrid.includes("magic")) {
                battleObj.player.magicLevel = skill.level;
            }
        }
        // Equipments
        battleObj.player.equipment = [];
        if (obj.profile.wearableItemMap) {
            for (const key in obj.profile.wearableItemMap) {
                const item = obj.profile.wearableItemMap[key];
                battleObj.player.equipment.push({
                    itemLocationHrid: item.itemLocationHrid,
                    itemHrid: item.itemHrid,
                    enhancementLevel: item.enhancementLevel,
                });
            }
        }
        // Food and Drinks
        battleObj.food = {}
        battleObj.food["/action_types/combat"] = [];
        battleObj.drinks = {}
        battleObj.drinks["/action_types/combat"] = [];
        let wearableItemMap = obj.profile.wearableItemMap;
        let weapon = null;
        if (wearableItemMap) {
            weapon = wearableItemMap["/item_locations/main_hand"]?.itemHrid ||
                wearableItemMap["/item_locations/two_hand"]?.itemHrid;
        }
        if (player) {
            battleObj.food = player.battleObj.food;
            battleObj.drinks = player.battleObj.drinks;
        } else if (weapon) {
            if (weapon.includes("shooter") || weapon.includes("bow")) {
                // 远程
                battleObj.food["/action_types/combat"] = [
                    // 2红1蓝
                    { itemHrid: "/items/spaceberry_donut" },
                    { itemHrid: "/items/spaceberry_cake" },
                    { itemHrid: "/items/star_fruit_yogurt" }
                ]
                battleObj.drinks["/action_types/combat"] = [
                    // 经验.超远.暴击
                    { itemHrid: "/items/wisdom_coffee" },
                    { itemHrid: "/items/super_ranged_coffee" },
                    { itemHrid: "/items/critical_coffee" }
                ]
            } else if (weapon.includes("boomstick") || weapon.includes("staff") || weapon.includes("trident")) {
                // 法师
                battleObj.food["/action_types/combat"] = [
                    // 1红2蓝
                    { itemHrid: "/items/spaceberry_cake" },
                    { itemHrid: "/items/star_fruit_gummy" },
                    { itemHrid: "/items/star_fruit_yogurt" }
                ]
                battleObj.drinks["/action_types/combat"] = [
                    // 经验.超魔.吟唱
                    { itemHrid: "/items/wisdom_coffee" },
                    { itemHrid: "/items/super_magic_coffee" },
                    { itemHrid: "/items/channeling_coffee" }
                ]
            } else if (weapon.includes("bulwark")) {
                // 双手盾
                battleObj.food["/action_types/combat"] = [
                    // 2红1蓝
                    { itemHrid: "/items/spaceberry_donut" },
                    { itemHrid: "/items/spaceberry_cake" },
                    { itemHrid: "/items/star_fruit_yogurt" }
                ]
                battleObj.drinks["/action_types/combat"] = [
                    // 经验.超防.超耐
                    { itemHrid: "/items/wisdom_coffee" },
                    { itemHrid: "/items/super_defense_coffee" },
                    { itemHrid: "/items/super_stamina_coffee" }
                ]
            } else {
                // 近战
                battleObj.food["/action_types/combat"] = [
                    // 2红1蓝
                    { itemHrid: "/items/spaceberry_donut" },
                    { itemHrid: "/items/spaceberry_cake" },
                    { itemHrid: "/items/star_fruit_yogurt" }
                ]
                battleObj.drinks["/action_types/combat"] = [
                    // 经验.超力.迅捷
                    { itemHrid: "/items/wisdom_coffee" },
                    { itemHrid: "/items/super_power_coffee" },
                    { itemHrid: "/items/swiftness_coffee" }
                ]
            }
        }
        // Abilities
        battleObj.abilities = [];
        for (let i = 0; i < 5; i++) {
            battleObj.abilities.push({
                abilityHrid: "",
                level: "1",
            })
        }
        if (obj.profile.equippedAbilities) {
            let index = 1;
            for (const ability of obj.profile.equippedAbilities) {
                if (ability && clientData.abilityDetailMap[ability.abilityHrid].isSpecialAbility) {
                    battleObj.abilities[0] = {
                        abilityHrid: ability.abilityHrid,
                        level: ability.level,
                        experience: ability.experience,
                        availableTime: ability.updatedAt
                    };
                } else if (ability) {
                    battleObj.abilities[index++] = {
                        abilityHrid: ability.abilityHrid,
                        level: ability.level,
                        experience: ability.experience,
                        availableTime: ability.updatedAt
                    };
                }
            }
        }
        // TriggerMap
        if (player) {
            battleObj.triggerMap = player.battleObj.triggerMap;
        }
        // HouseRooms
        battleObj.houseRooms = {};
        for (const house of Object.values(obj.profile.characterHouseRoomMap)) {
            battleObj.houseRooms[house.houseRoomHrid] = house.level;
        }
        return battleObj;
    }

    // 构建战斗模拟信息(NewBattle)
    function buildBattleObjFromNewBattle(player, obj) {
        let battleObj = {};
        if (player) {
            battleObj = player.battleObj;
        }
        // Base
        battleObj.character = battleObj.character ?? {};
        battleObj.character.id = obj.character.id;
        battleObj.character.name = obj.character.name;
        battleObj.character.gameMode = obj.character.gameMode;
        battleObj.timestamp = Date.now();
        battleObj.valid = battleObj.valid;
        // Levels
        battleObj.player = battleObj.player ?? {};
        battleObj.player.staminaLevel = battleObj.player.staminaLevel ?? 1;
        battleObj.player.intelligenceLevel = battleObj.player.intelligenceLevel ?? 1;
        battleObj.player.attackLevel = battleObj.player.attackLevel ?? 1;
        battleObj.player.powerLevel = battleObj.player.powerLevel ?? 1;
        battleObj.player.defenseLevel = battleObj.player.defenseLevel ?? 1;
        battleObj.player.rangedLevel = battleObj.player.rangedLevel ?? 1;
        battleObj.player.magicLevel = battleObj.player.magicLevel ?? 1;
        // Equipments
        battleObj.player.equipment = battleObj.player.equipment ?? [];
        // Food and Drinks
        battleObj.food = {};
        battleObj.food["/action_types/combat"] = [];
        battleObj.drinks = {};
        battleObj.drinks["/action_types/combat"] = [];
        if (obj.combatConsumables) {
            for (const consumable of obj.combatConsumables) {
                if (consumable.itemHrid.includes("coffee")) {
                    battleObj.drinks["/action_types/combat"].push({
                        itemHrid: consumable.itemHrid
                    })
                } else {
                    battleObj.food["/action_types/combat"].push({
                        itemHrid: consumable.itemHrid
                    })
                }
            }
        }
        // Abilities
        battleObj.abilities = [];
        for (let i = 0; i < 5; i++) {
            battleObj.abilities.push({
                abilityHrid: "",
                level: "1",
            })
        }
        if (obj.combatAbilities) {
            let index = 1;
            for (const ability of obj.combatAbilities) {
                if (ability && clientData.abilityDetailMap[ability.abilityHrid].isSpecialAbility) {
                    battleObj.abilities[0] = {
                        abilityHrid: ability.abilityHrid,
                        level: ability.level,
                        experience: ability.experience,
                        availableTime: ability.updatedAt
                    };
                } else if (ability) {
                    battleObj.abilities[index++] = {
                        abilityHrid: ability.abilityHrid,
                        level: ability.level,
                        experience: ability.experience,
                        availableTime: ability.updatedAt
                    };
                }
            }
        }
        // TriggerMap
        battleObj.triggerMap = { ...battleObj.triggerMap };
        // HouseRooms
        battleObj.houseRooms = { ...battleObj.houseRooms };
        return battleObj;
    }

    if (localStorage.getItem("initClientData")) {
        const obj = JSON.parse(localStorage.getItem("initClientData"));
        GM_setValue("init_client_data", localStorage.getItem("initClientData"));

        clientData.actionDetailMap = obj.actionDetailMap;
        clientData.levelExperienceTable = obj.levelExperienceTable;
        clientData.itemDetailMap = obj.itemDetailMap;
        clientData.actionCategoryDetailMap = obj.actionCategoryDetailMap;
        clientData.abilityDetailMap = obj.abilityDetailMap;
    }

    if (document.URL.includes("/MWICombatSimulatorTest/dist")) {
        addImportButtonForMWICombatSimulate();
        observeResultsForMWICombatSimulate();
    }

    hookWS();

})();