// ==UserScript== // @name MWI TaskManager // @namespace http://tampermonkey.net/ // @version 0.16 // @description sort all task in taskboard // @author shykai // @match https://www.milkywayidle.com/* // @match https://test.milkywayidle.com/* // @icon https://www.milkywayidle.com/favicon.svg // @grant GM_getValue // @grant GM_setValue // @downloadURL none // ==/UserScript== (function () { 'use strict'; //default config let globalConfig = { isBattleIcon: true, dungeonConfig: { "/actions/combat/chimerical_den": false, "/actions/combat/sinister_circus": false, "/actions/combat/enchanted_fortress": false, } }; const globalConfigName = "MWITaskManager_globalConfig"; function saveConfig() { GM_setValue(globalConfigName, JSON.stringify(globalConfig)); } const savedConfig = GM_getValue(globalConfigName, null); if (savedConfig) { Object.assign(globalConfig, JSON.parse(savedConfig)); } const taskBattleIndex = 99; //Battle at bottom const taskOrderIndex = { Milking: 1, Foraging: 2, Woodcutting: 3, Cheesesmithing: 4, Crafting: 5, Tailoring: 6, Cooking: 7, Brewing: 8, Alchemy: 9, Enhancing: 10, Defeat: taskBattleIndex, //Battle at bottom }; const taskOrderIndex_CN = { 挤奶: 1, 采摘: 2, 伐木: 3, 奶酪锻造: 4, 制作: 5, 缝纫: 6, 烹饪: 7, 冲泡: 8, 炼金: 9, 强化: 10, 击败: taskBattleIndex, //Battle at bottom }; const allMonster = { "/monsters/abyssal_imp": { "en": "Abyssal Imp", "cn": "深渊小鬼", "zone": "/actions/combat/infernal_abyss", "dungeon": [ "/actions/combat/enchanted_fortress" ], "sortIndex": 11 }, "/monsters/aquahorse": { "en": "Aquahorse", "cn": "水马", "zone": "/actions/combat/aqua_planet", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": 3 }, "/monsters/black_bear": { "en": "Black Bear", "cn": "黑熊", "zone": "/actions/combat/bear_with_it", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 8 }, "/monsters/gobo_boomy": { "en": "Boomy", "cn": "轰轰", "zone": "/actions/combat/gobo_planet", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 5 }, "/monsters/butterjerry": { "en": "Butterjerry", "cn": "蝶鼠", "zone": "", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": -1 }, "/monsters/centaur_archer": { "en": "Centaur Archer", "cn": "半人马弓箭手", "zone": "/actions/combat/jungle_planet", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": 4 }, "/monsters/chronofrost_sorcerer": { "en": "Chronofrost Sorcerer", "cn": "霜时巫师", "zone": "/actions/combat/sorcerers_tower", "sortIndex": 7 }, "/monsters/crystal_colossus": { "en": "Crystal Colossus", "cn": "水晶巨像", "zone": "/actions/combat/golem_cave", "sortIndex": 9 }, "/monsters/demonic_overlord": { "en": "Demonic Overlord", "cn": "恶魔霸主", "zone": "/actions/combat/infernal_abyss", "sortIndex": 11 }, "/monsters/dusk_revenant": { "en": "Dusk Revenant", "cn": "黄昏亡灵", "zone": "/actions/combat/twilight_zone", "sortIndex": 10 }, "/monsters/elementalist": { "en": "Elementalist", "cn": "元素法师", "zone": "/actions/combat/sorcerers_tower", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 7 }, "/monsters/enchanted_pawn": { "en": "Enchanted Pawn", "cn": "秘法之兵", "zone": "", "dungeon": [ "/actions/combat/enchanted_fortress" ], "sortIndex": -1 }, "/monsters/eye": { "en": "Eye", "cn": "独眼", "zone": "/actions/combat/planet_of_the_eyes", "dungeon": [ "/actions/combat/enchanted_fortress" ], "sortIndex": 6 }, "/monsters/eyes": { "en": "Eyes", "cn": "叠眼", "zone": "/actions/combat/planet_of_the_eyes", "dungeon": [ "/actions/combat/enchanted_fortress" ], "sortIndex": 6 }, "/monsters/flame_sorcerer": { "en": "Flame Sorcerer", "cn": "火焰巫师", "zone": "/actions/combat/sorcerers_tower", "dungeon": [ "/actions/combat/enchanted_fortress", "/actions/combat/sinister_circus" ], "sortIndex": 7 }, "/monsters/fly": { "en": "Fly", "cn": "苍蝇", "zone": "/actions/combat/smelly_planet", "sortIndex": 1 }, "/monsters/frog": { "en": "Frogger", "cn": "青蛙", "zone": "/actions/combat/swamp_planet", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": 2 }, "/monsters/sea_snail": { "en": "Gary", "cn": "蜗牛", "zone": "/actions/combat/aqua_planet", "sortIndex": 3 }, "/monsters/giant_shoebill": { "en": "Giant Shoebill", "cn": "鲸头鹳", "zone": "/actions/combat/swamp_planet", "sortIndex": 2 }, "/monsters/gobo_chieftain": { "en": "Gobo Chieftain", "cn": "哥布林酋长", "zone": "/actions/combat/gobo_planet", "sortIndex": 5 }, "/monsters/granite_golem": { "en": "Granite Golem", "cn": "花岗魔像", "zone": "/actions/combat/golem_cave", "sortIndex": 9 }, "/monsters/grizzly_bear": { "en": "Grizzly Bear", "cn": "棕熊", "zone": "/actions/combat/bear_with_it", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 8 }, "/monsters/gummy_bear": { "en": "Gummy Bear", "cn": "软糖熊", "zone": "/actions/combat/bear_with_it", "dungeon": [ "/actions/combat/chimerical_den", "/actions/combat/sinister_circus" ], "sortIndex": 8 }, "/monsters/crab": { "en": "I Pinch", "cn": "螃蟹", "zone": "/actions/combat/aqua_planet", "sortIndex": 3 }, "/monsters/ice_sorcerer": { "en": "Ice Sorcerer", "cn": "冰霜巫师", "zone": "/actions/combat/sorcerers_tower", "dungeon": [ "/actions/combat/enchanted_fortress", "/actions/combat/sinister_circus" ], "sortIndex": 7 }, "/monsters/infernal_warlock": { "en": "Infernal Warlock", "cn": "地狱术士", "zone": "/actions/combat/infernal_abyss", "sortIndex": 11 }, "/monsters/jackalope": { "en": "Jackalope", "cn": "鹿角兔", "zone": "", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": -1 }, "/monsters/rat": { "en": "Jerry", "cn": "杰瑞", "zone": "/actions/combat/smelly_planet", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": 1 }, "/monsters/jungle_sprite": { "en": "Jungle Sprite", "cn": "丛林精灵", "zone": "/actions/combat/jungle_planet", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": 4 }, "/monsters/luna_empress": { "en": "Luna Empress", "cn": "月神之蝶", "zone": "/actions/combat/jungle_planet", "sortIndex": 4 }, "/monsters/magnetic_golem": { "en": "Magnetic Golem", "cn": "磁力魔像", "zone": "/actions/combat/golem_cave", "dungeon": [ "/actions/combat/enchanted_fortress" ], "sortIndex": 9 }, "/monsters/marine_huntress": { "en": "Marine Huntress", "cn": "海洋猎手", "zone": "/actions/combat/aqua_planet", "sortIndex": 3 }, "/monsters/myconid": { "en": "Myconid", "cn": "蘑菇人", "zone": "/actions/combat/jungle_planet", "sortIndex": 4 }, "/monsters/nom_nom": { "en": "Nom Nom", "cn": "咬咬鱼", "zone": "/actions/combat/aqua_planet", "sortIndex": 3 }, "/monsters/novice_sorcerer": { "en": "Novice Sorcerer", "cn": "新手巫师", "zone": "/actions/combat/sorcerers_tower", "dungeon": [ "/actions/combat/enchanted_fortress", "/actions/combat/sinister_circus" ], "sortIndex": 7 }, "/monsters/panda": { "en": "Panda", "cn": "熊猫", "zone": "/actions/combat/bear_with_it", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 8 }, "/monsters/polar_bear": { "en": "Polar Bear", "cn": "北极熊", "zone": "/actions/combat/bear_with_it", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 8 }, "/monsters/porcupine": { "en": "Porcupine", "cn": "豪猪", "zone": "/actions/combat/smelly_planet", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": 1 }, "/monsters/rabid_rabbit": { "en": "Rabid Rabbit", "cn": "疯魔兔", "zone": "", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": -1 }, "/monsters/red_panda": { "en": "Red Panda", "cn": "小熊猫", "zone": "/actions/combat/bear_with_it", "sortIndex": 8 }, "/monsters/alligator": { "en": "Sherlock", "cn": "夏洛克", "zone": "/actions/combat/swamp_planet", "sortIndex": 2 }, "/monsters/gobo_shooty": { "en": "Shooty", "cn": "咻咻", "zone": "/actions/combat/gobo_planet", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 5 }, "/monsters/skunk": { "en": "Skunk", "cn": "臭鼬", "zone": "/actions/combat/smelly_planet", "sortIndex": 1 }, "/monsters/gobo_slashy": { "en": "Slashy", "cn": "砍砍", "zone": "/actions/combat/gobo_planet", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 5 }, "/monsters/slimy": { "en": "Slimy", "cn": "史莱姆", "zone": "/actions/combat/smelly_planet", "sortIndex": 1 }, "/monsters/gobo_smashy": { "en": "Smashy", "cn": "锤锤", "zone": "/actions/combat/gobo_planet", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 5 }, "/monsters/soul_hunter": { "en": "Soul Hunter", "cn": "灵魂猎手", "zone": "/actions/combat/infernal_abyss", "dungeon": [ "/actions/combat/enchanted_fortress" ], "sortIndex": 11 }, "/monsters/gobo_stabby": { "en": "Stabby", "cn": "刺刺", "zone": "/actions/combat/gobo_planet", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 5 }, "/monsters/stalactite_golem": { "en": "Stalactite Golem", "cn": "钟乳石魔像", "zone": "/actions/combat/golem_cave", "dungeon": [ "/actions/combat/enchanted_fortress" ], "sortIndex": 9 }, "/monsters/swampy": { "en": "Swampy", "cn": "沼泽虫", "zone": "/actions/combat/swamp_planet", "dungeon": [ "/actions/combat/chimerical_den" ], "sortIndex": 2 }, "/monsters/the_watcher": { "en": "The Watcher", "cn": "观察者", "zone": "/actions/combat/planet_of_the_eyes", "sortIndex": 6 }, "/monsters/snake": { "en": "Thnake", "cn": "蛇", "zone": "/actions/combat/swamp_planet", "sortIndex": 2 }, "/monsters/treant": { "en": "Treant", "cn": "树人", "zone": "/actions/combat/jungle_planet", "sortIndex": 4 }, "/monsters/turtle": { "en": "Turuto", "cn": "忍者龟", "zone": "/actions/combat/aqua_planet", "sortIndex": 3 }, "/monsters/vampire": { "en": "Vampire", "cn": "吸血鬼", "zone": "/actions/combat/twilight_zone", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 10 }, "/monsters/veyes": { "en": "Veyes", "cn": "复眼", "zone": "/actions/combat/planet_of_the_eyes", "dungeon": [ "/actions/combat/enchanted_fortress" ], "sortIndex": 6 }, "/monsters/werewolf": { "en": "Werewolf", "cn": "狼人", "zone": "/actions/combat/twilight_zone", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 10 }, "/monsters/zombie": { "en": "Zombie", "cn": "僵尸", "zone": "/actions/combat/twilight_zone", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": 10 }, "/monsters/zombie_bear": { "en": "Zombie Bear", "cn": "僵尸熊", "zone": "", "dungeon": [ "/actions/combat/sinister_circus" ], "sortIndex": -1 } }; function getTaskDetailFromTaskName(fullTaskName) { var taskType = -1; var taskName = ""; if (/^(.+) - (.+)$/.test(fullTaskName)) { let res = /^(.+) - (.+)$/.exec(fullTaskName); if (res[1] in taskOrderIndex) { taskType = taskOrderIndex[res[1]]; } else if (res[1] in taskOrderIndex_CN) { taskType = taskOrderIndex_CN[res[1]]; } taskName = res[2]; } if (taskType == -1) console.log("Task Parse error", fullTaskName); return { taskType, taskName }; } function getHridFromMonsterName(name) { for (let key in allMonster) { if (allMonster[key].en === name || allMonster[key].cn === name) { return key; } } console.log("Monster not found", name); return null; } function getMapIndexFromMonsterName(name) { const key = getHridFromMonsterName(name); if (!key) { return -1; } return allMonster[key].sortIndex; } function getTaskDetailFromElement(ele) { const div = ele.querySelector("div.RandomTask_name__1hl1b"); const translatedfrom = div.getAttribute("script_translatedfrom"); //adapt old zhCN Script if (translatedfrom) { return getTaskDetailFromTaskName(translatedfrom); } const fullTaskName = Array.from(div.childNodes).find(node => node.nodeType === Node.TEXT_NODE).textContent.trim(); return getTaskDetailFromTaskName(fullTaskName); } function compareFn(a, b) { var { taskType: a_TypeIndex, taskName: a_taskName } = getTaskDetailFromElement(a); var { taskType: b_TypeIndex, taskName: b_TaskName } = getTaskDetailFromElement(b); if (a_TypeIndex === taskBattleIndex && b_TypeIndex === taskBattleIndex) { var a_MapIndex = getMapIndexFromMonsterName(a_taskName); var b_MapIndex = getMapIndexFromMonsterName(b_TaskName); if (a_MapIndex != b_MapIndex) { return (a_MapIndex > b_MapIndex ? 1 : -1); } } if (a_TypeIndex == b_TypeIndex) { return a_taskName == b_TaskName ? 0 : (a_taskName > b_TaskName ? 1 : -1); } return a_TypeIndex > b_TypeIndex ? 1 : -1; } function addIconToTask(div) { var { taskType, taskName } = getTaskDetailFromElement(div); if (taskType != taskBattleIndex) { return; } const monsterHrid = getHridFromMonsterName(taskName); if (!monsterHrid) { return; } var offset = 5; // 5% from left and each 30% width const isShowDungeon = Object.values(globalConfig.dungeonConfig).filter(Boolean).length > 0; if (!isShowDungeon) { offset = 50; } const backgroundDiv = document.createElement('div'); backgroundDiv.id = "MonsterIcon"; backgroundDiv.style.position = 'absolute'; backgroundDiv.style.left = `${offset}%`; offset += 30; backgroundDiv.style.width = '30%'; backgroundDiv.style.height = '100%'; backgroundDiv.style.opacity = '0.3'; const monsterName = monsterHrid.split("/").pop(); const svgContent = ``; backgroundDiv.innerHTML = svgContent; div.appendChild(backgroundDiv); const dungeonMap = allMonster[monsterHrid]?.dungeon; if (isShowDungeon && dungeonMap) { Object.keys(globalConfig.dungeonConfig).filter(dungeon => globalConfig.dungeonConfig[dungeon]).forEach(dungeon => { if (dungeonMap.includes(dungeon)) { const dungeonDiv = document.createElement('div'); dungeonDiv.id = "DungeonIcon"; dungeonDiv.style.position = 'absolute'; dungeonDiv.style.left = `${offset}%`; offset += 30; dungeonDiv.style.width = '30%'; dungeonDiv.style.height = '100%'; dungeonDiv.style.opacity = '0.3'; const dungeonName = dungeon.split("/").pop(); const svgContent = ``; dungeonDiv.innerHTML = svgContent; div.appendChild(dungeonDiv); } }) } // fix button style div.style.position = 'relative'; div.querySelector(".RandomTask_content__VVQva").style.zIndex = 1; div.querySelectorAll(".Item_item__2De2O").forEach(node => node.style.backgroundColor = "transparent"); } function updateIconByConfig() { const battleIcon = document.querySelector("#BattleIcon"); if (battleIcon) { if (globalConfig.isBattleIcon) { battleIcon.style.opacity = '1'; battleIcon.querySelector("#taskCount").style.display = 'inline'; } else { battleIcon.style.opacity = '0.3'; battleIcon.querySelector("#taskCount").style.display = 'none'; } } Object.keys(globalConfig.dungeonConfig).forEach(dungeon => { const dungeonIcon = document.querySelector(`#${dungeon.split("/").pop()}`); if (dungeonIcon) { if (globalConfig.isBattleIcon && globalConfig.dungeonConfig[dungeon]) { dungeonIcon.style.opacity = '1'; dungeonIcon.querySelector("#taskCount").style.display = 'inline'; } else { dungeonIcon.style.opacity = '0.3'; dungeonIcon.querySelector("#taskCount").style.display = 'none'; } } }); } function createIcon(id,href) { // battle icon const div = document.createElement("div"); div.id = id; div.style.height = "100%"; // 设置高度 const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("role", "img"); svg.setAttribute("aria-label", "Combat"); svg.setAttribute("class", "Icon_icon__2LtL_ Icon_xtiny__331pI Icon_inline__1Idwv"); svg.setAttribute("width", "100%"); svg.setAttribute("height", "100%"); svg.style.margin = "1px"; const use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttribute("href", href); svg.appendChild(use); const divCount = document.createElement("span"); divCount.id = "taskCount"; divCount.textContent = ""; div.appendChild(svg); div.appendChild(divCount); // div onclick change config div.addEventListener("click", function (evt) { if (id === "BattleIcon") { globalConfig.isBattleIcon = !globalConfig.isBattleIcon; } else { let configkey = Object.keys(globalConfig.dungeonConfig).find(key => key.split("/").pop() === id); globalConfig.dungeonConfig[configkey] = !globalConfig.dungeonConfig[configkey]; } saveConfig(); //auto save when click updateIconByConfig(); //clean all checkers to refresh statics document.querySelectorAll("#taskChekerInCoin").forEach(checker => checker.id = null); }); return div; } function addSortButtonAndStaticsBar(pannel) { const sortButton = document.createElement("button"); sortButton.setAttribute("class", "Button_button__1Fe9z Button_small__3fqC7"); sortButton.id = "TaskSort"; sortButton.innerHTML = "TaskSort"; sortButton.addEventListener("click", function (evt) { const list = document.querySelector("div.TasksPanel_taskList__2xh4k"); [...list.querySelectorAll("div.RandomTask_randomTask__3B9fA")] .sort(compareFn) .forEach(node => list.appendChild(node)); }); pannel.appendChild(sortButton); // add statics bar const battleIcon = createIcon("BattleIcon", "/static/media/misc_sprite.426c5d78.svg#combat"); pannel.appendChild(battleIcon); // add all dungeon icon Object.keys(globalConfig.dungeonConfig).forEach(dungeon => { const dungeonIcon = createIcon(dungeon.split("/").pop(), `/static/media/actions_sprite.8d5ceb4a.svg#${dungeon.split("/").pop()}`); pannel.appendChild(dungeonIcon); }); } function optimizeForMobile(pannel) { if (/Mobi|Android/i.test(navigator.userAgent)) { const upgradeButton = pannel.querySelector("button.Button_button__1Fe9z.Button_small__3fqC7"); if (upgradeButton) { upgradeButton.style.display = "none"; console.log("hide upgrade button when mobile"); } } } function refresh() { const pannel = document.querySelector("div.TasksPanel_taskSlotCount__nfhgS"); if (pannel) { let sortButton = pannel.querySelector("#TaskSort"); if (!sortButton) { optimizeForMobile(pannel); addSortButtonAndStaticsBar(pannel); updateIconByConfig(); } } else { return; //not in task board } let needRefreshTaskStatics = false; const taskNodes = document.querySelectorAll("div.TasksPanel_taskList__2xh4k div.RandomTask_randomTask__3B9fA"); for (let node of taskNodes) { const coinDiv = node.querySelector(".Item_count__1HVvv"); if (coinDiv && !coinDiv.querySelector("#taskChekerInCoin")) { needRefreshTaskStatics = true; //remove old and add new icon const oldIcon = node.querySelector("#MonsterIcon"); if (oldIcon) { oldIcon.remove(); } const oldDungeonIcons = node.querySelectorAll("#DungeonIcon"); oldDungeonIcons.forEach(icon => icon.remove()); if (globalConfig.isBattleIcon) { addIconToTask(node); } //add checker const checker = document.createElement("div"); checker.id = "taskChekerInCoin"; coinDiv.appendChild(checker); } } if (needRefreshTaskStatics) { const battleIcon = document.querySelector("#BattleIcon #taskCount"); if (battleIcon) { const battleCount = [...document.querySelectorAll("div.RandomTask_randomTask__3B9fA")].filter(node => node.querySelector("#MonsterIcon")).length; battleIcon.textContent = battleCount > 0 ? `*${battleCount}` : ''; } Object.keys(globalConfig.dungeonConfig).forEach(dungeon => { const dungeonIcon = document.querySelector(`#${dungeon.split("/").pop()} #taskCount`); if (dungeonIcon) { const dungeonCount = [...document.querySelectorAll("div.RandomTask_randomTask__3B9fA")].filter(node => { const dungeonIcons = node.querySelectorAll("#DungeonIcon use"); return Array.from(dungeonIcons).some(icon => icon.getAttribute("href").includes(dungeon.split("/").pop())); }).length; dungeonIcon.textContent = dungeonCount > 0 ? `*${dungeonCount}` : ''; } }); } } const config = { attributes: true, childList: true, subtree: true }; const observer = new MutationObserver(function (mutationsList, observer) { refresh(); }); observer.observe(document, config); })();