// ==UserScript==
// @name Ranged Way Idle
// @namespace http://tampermonkey.net/
// @version 1.11
// @description 死亡提醒、强制刷新MWITools的价格、私信提醒音、自动任务排序、显示购买预付金/出售可获金/待领取金额、显示任务价值、默哀法师助手
// @author AlphB
// @match https://www.milkywayidle.com/*
// @match https://test.milkywayidle.com/*
// @grant GM_notification
// @grant GM_getValue
// @grant GM_setValue
// @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
// @grant none
// @license CC-BY-NC-SA-4.0
// ==/UserScript==
(function () {
const config = {
notifyDeath: true,
forceUpdateMarketPrice: true,
notifyWhisperMessages: false,
listenKeywordMessages: false,
autoTaskSort: true,
showMarketListingsFunds: true,
mournForMagicWayIdle: true,
showTaskValue: true,
keywords: [],
}
const globalVariable = {
battleData: {
players: null
},
itemDetailMap: JSON.parse(localStorage.getItem("initClientData")).itemDetailMap,
whisperAudio: new Audio(`https://upload.thbwiki.cc/d/d1/se_bonus2.mp3`),
keywordAudio: new Audio(`https://upload.thbwiki.cc/c/c9/se_pldead00.mp3`),
market: {
hasFundsElement: false,
sellValue: null,
buyValue: null,
unclaimedValue: null,
sellListings: null,
buyListings: null
},
task: {
taskListElement: null,
taskTokenValueData: null,
hasTaskValueElement: false,
taskValueElements: [],
tokenValue: {
Bid: null,
Ask: null
}
}
};
init();
function init() {
// readConfig();
if (!('Edible_Tools' in localStorage)) {
config.showTaskValue = false;
}
globalVariable.whisperAudio.volume = 0.4;
globalVariable.keywordAudio.volume = 0.4;
let observer = new MutationObserver(function () {
if (config.showMarketListingsFunds) showMarketListingsFunds();
if (config.autoTaskSort) autoClickTaskSortButton();
if (config.showTaskValue) showTaskValue();
});
observer.observe(document, {childList: true, subtree: true});
const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
const oriGet = dataProperty.get;
dataProperty.get = hookedGet;
Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
globalVariable.task.taskTokenValueData = getTaskTokenValue();
if (config.mournForMagicWayIdle) {
console.log("为法师助手默哀");
}
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});
return handleMessage(message);
}
// function readConfig() {
// const localConfig = localStorage.getItem("ranged_way_idle_config");
// if (localConfig) {
// const localConfigObj = JSON.parse(localConfig);
// for (let key in localConfigObj) {
// if (config.hasOwnProperty(key)) {
// config[key] = localConfigObj[key];
// }
// }
// }
// }
}
function handleMessage(message) {
const obj = JSON.parse(message);
if (!obj) return message;
switch (obj.type) {
case "init_character_data":
globalVariable.market.sellListings = {};
globalVariable.market.buyListings = {};
config.keywords.push(obj.character.name.toLowerCase());
updateMarketListings(obj.myMarketListings);
break;
case "market_listings_updated":
updateMarketListings(obj.endMarketListings);
break;
case "new_battle":
if (config.notifyDeath) initBattle(obj);
break;
case "battle_updated":
if (config.notifyDeath) checkDeath(obj);
break;
case "market_item_order_books_updated":
if (config.forceUpdateMarketPrice) marketPriceUpdate(obj);
break;
case "quests_updated":
for (let e of globalVariable.task.taskValueElements) {
e.remove();
}
globalVariable.task.taskValueElements = [];
globalVariable.task.hasTaskValueElement = false;
break;
case "chat_message_received":
handleChatMessage(obj);
break;
}
return message;
}
function notifyDeath(name) {
new Notification('🎉🎉🎉喜报🎉🎉🎉', {body: `${name} 死了!`});
}
function initBattle(obj) {
globalVariable.battleData.players = [];
for (let player of obj.players) {
globalVariable.battleData.players.push({
name: player.name, isAlive: player.currentHitpoints > 0,
});
if (player.currentHitpoints === 0) {
notifyDeath(player.name);
}
}
}
function checkDeath(obj) {
if (!globalVariable.battleData.players) return;
for (let key in obj.pMap) {
const index = parseInt(key);
if (globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP === 0) {
globalVariable.battleData.players[index].isAlive = false;
notifyDeath(globalVariable.battleData.players[index].name);
} else if (!globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP > 0) {
globalVariable.battleData.players[index].isAlive = true;
}
}
}
function marketPriceUpdate(obj) {
globalVariable.task.taskTokenValueData = getTaskTokenValue();
// 本函数的代码复制自Magic Way Idle
let itemDetailMap = globalVariable.itemDetailMap;
let itemName = itemDetailMap[obj.marketItemOrderBooks.itemHrid].name;
let ask = -1;
let bid = -1;
// 读取ask最低报价
if (obj.marketItemOrderBooks.orderBooks[0].asks && obj.marketItemOrderBooks.orderBooks[0].asks.length > 0) {
ask = obj.marketItemOrderBooks.orderBooks[0].asks[0].price;
}
// 读取bid最高报价
if (obj.marketItemOrderBooks.orderBooks[0].bids && obj.marketItemOrderBooks.orderBooks[0].bids.length > 0) {
bid = obj.marketItemOrderBooks.orderBooks[0].bids[0].price;
}
// 读取所有物品价格
let jsonObj = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
// 修改当前查看物品价格
if (jsonObj.market[itemName]) {
jsonObj.market[itemName].ask = ask;
jsonObj.market[itemName].bid = bid;
}
// 将修改后结果写回marketAPI缓存,完成对marketAPI价格的强制修改
localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(jsonObj));
}
function handleChatMessage(obj) {
if (obj.message.chan === "/chat_channel_types/whisper") {
if (config.notifyWhisperMessages) {
globalVariable.whisperAudio.play();
}
} else if (obj.message.chan === "/chat_channel_types/chinese") {
if (config.listenKeywordMessages) {
for (let keyword of config.keywords) {
if (obj.message.m.toLowerCase().includes(keyword)) {
globalVariable.keywordAudio.play();
}
}
}
}
}
function autoClickTaskSortButton() {
const targetElement = document.querySelector('#TaskSort');
if (targetElement && targetElement.textContent !== '手动排序') {
targetElement.click();
targetElement.textContent = '手动排序';
}
}
function formatCoinValue(num) {
if (num >= 1e13) {
return Math.floor(num / 1e12) + "T";
} else if (num >= 1e10) {
return Math.floor(num / 1e9) + "B";
} else if (num >= 1e7) {
return Math.floor(num / 1e6) + "M";
} else if (num >= 1e4) {
return Math.floor(num / 1e3) + "K";
}
return num.toString();
}
function updateMarketListings(obj) {
for (let listing of obj) {
if (listing.status === "/market_listing_status/cancelled") {
delete globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id];
continue
}
globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id] = {
itemHrid: listing.itemHrid,
price: (listing.orderQuantity - listing.filledQuantity) * (listing.isSell ? Math.ceil(listing.price * 0.98) : listing.price),
unclaimedCoinCount: listing.unclaimedCoinCount,
}
}
globalVariable.market.buyValue = 0;
globalVariable.market.sellValue = 0;
globalVariable.market.unclaimedValue = 0;
for (let id in globalVariable.market.buyListings) {
const listing = globalVariable.market.buyListings[id];
globalVariable.market.buyValue += listing.price;
globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
}
for (let id in globalVariable.market.sellListings) {
const listing = globalVariable.market.sellListings[id];
globalVariable.market.sellValue += listing.price;
globalVariable.market.unclaimedValue += listing.unclaimedCoinCount;
}
globalVariable.market.hasFundsElement = false;
}
function showMarketListingsFunds() {
if (globalVariable.market.hasFundsElement) return;
const coinStackElement = document.querySelector("div.MarketplacePanel_coinStack__1l0UD");
if (coinStackElement) {
coinStackElement.style.top = "0px";
coinStackElement.style.left = "0px";
let fundsElement = coinStackElement.parentNode.querySelector("div.fundsElement");
while (fundsElement) {
fundsElement.remove();
fundsElement = coinStackElement.parentNode.querySelector("div.fundsElement");
}
makeNode("购买预付金", globalVariable.market.buyValue, ["125px", "0px"]);
makeNode("出售可获金", globalVariable.market.sellValue, ["125px", "22px"]);
makeNode("待领取金额", globalVariable.market.unclaimedValue, ["0px", "22px"]);
globalVariable.market.hasFundsElement = true;
}
function makeNode(text, value, style) {
let node = coinStackElement.cloneNode(true);
node.classList.add("fundsElement");
const countNode = node.querySelector("div.Item_count__1HVvv");
const textNode = node.querySelector("div.Item_name__2C42x");
if (countNode) countNode.textContent = formatCoinValue(value);
if (textNode) textNode.innerHTML = `<span style="color: rgb(102,204,255); font-weight: bold;">${text}</span>`;
node.style.left = style[0];
node.style.top = style[1];
coinStackElement.parentNode.insertBefore(node, coinStackElement.nextSibling);
}
}
function getTaskTokenValue() {
const chestDropData = JSON.parse(localStorage.getItem("Edible_Tools")).Chest_Drop_Data;
const lootsName = ["大陨石舱", "大工匠匣", "大宝箱"];
const bidValueList = [
parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Bid"]),
parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Bid"]),
parseFloat(chestDropData["Large Treasure Chest"]["期望产出Bid"]),
]
const askValueList = [
parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Ask"]),
parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Ask"]),
parseFloat(chestDropData["Large Treasure Chest"]["期望产出Ask"]),
]
const res = {
bidValue: Math.max(...bidValueList),
askValue: Math.max(...askValueList)
}
res.bidLoots = lootsName[bidValueList.indexOf(res.bidValue)];
res.askLoots = lootsName[askValueList.indexOf(res.askValue)];
res.bidValue = Math.round(res.bidValue / 30);
res.askValue = Math.round(res.askValue / 30);
res.giftValueBid = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Bid"]));
res.giftValueAsk = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Ask"]));
if (config.forceUpdateMarketPrice) {
const marketJSON = JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
marketJSON.market["Task Token"].ask = res.askValue;
marketJSON.market["Task Token"].bid = res.bidValue;
localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(marketJSON));
}
res.rewardValueBid = res.bidValue + res.giftValueBid / 50;
res.rewardValueAsk = res.askValue + res.giftValueAsk / 50;
return res;
}
function showTaskValue() {
globalVariable.task.taskListElement = document.querySelector("div.TasksPanel_taskList__2xh4k");
if (!globalVariable.task.taskListElement) {
globalVariable.task.taskValueElements = [];
globalVariable.task.hasTaskValueElement = false;
globalVariable.task.taskListElement = null;
return;
}
if (globalVariable.task.hasTaskValueElement) return;
globalVariable.task.hasTaskValueElement = true;
const taskNodes = [...globalVariable.task.taskListElement.querySelectorAll("div.RandomTask_randomTask__3B9fA")];
function convertKEndStringToNumber(str) {
if (str.endsWith('K') || str.endsWith('k')) {
return Number(str.slice(0, -1)) * 1000;
} else {
return Number(str);
}
}
taskNodes.forEach(function (node) {
const reward = node.querySelector("div.RandomTask_rewards__YZk7D");
const coin = convertKEndStringToNumber(reward.querySelectorAll("div.Item_count__1HVvv")[0].innerText);
const tokenCount = Number(reward.querySelectorAll("div.Item_count__1HVvv")[1].innerText);
const newDiv = document.createElement("div");
newDiv.textContent = `奖励期望收益:
${formatCoinValue(coin + tokenCount * globalVariable.task.taskTokenValueData.rewardValueAsk)} /
${formatCoinValue(coin + tokenCount * globalVariable.task.taskTokenValueData.rewardValueBid)}`;
newDiv.style.color = "rgb(248,0,248)";
node.querySelector("div.RandomTask_action__3eC6o").appendChild(newDiv);
globalVariable.task.taskValueElements.push(newDiv);
});
}
})();