// ==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
// ==/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 = `<svg width="100%" height="100%"><use href="/static/media/combat_monsters_sprite.395438a8.svg#${monsterName}"></use></svg>`;
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 = `<svg width="100%" height="100%"><use href="/static/media/actions_sprite.8d5ceb4a.svg#${dungeonName}"></use></svg>`;
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);
})();