// ==UserScript==
// @name [银河奶牛]食用工具
// @namespace http://tampermonkey.net/
// @version 0.467
// @description 开箱记录、箱子期望、离线统计、公会钉钉、食物警察、掉落追踪、强化统计
// @author Truth_Light
// @license Truth_Light
// @match https://www.milkywayidle.com/*
// @match https://test.milkywayidle.com/*
// @icon https://www.milkywayidle.com/favicon.svg
// @grant GM.xmlHttpRequest
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(async function() {
'use strict';
const isCN = !['en'].some(lang =>localStorage.getItem("i18nextLng")?.toLowerCase()?.startsWith(lang));
const itemSelector = '.ItemDictionary_drop__24I5f';
const iconSelector = '.Icon_icon__2LtL_ use';
const chestNameSelector = '#root > div > div > div.Modal_modalContainer__3B80m > div.Modal_modal__1Jiep > div.ItemDictionary_modalContent__WvEBY > div.ItemDictionary_itemAndDescription__28_he > div.Item_itemContainer__x7kH1 > div > div > div > svg > use';
const MARKET_API_URL = "https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json";
let timer = null;
let formattedChestDropData = {};
let battlePlayerFood = {};
let battlePlayerLoot = {};
let battlePlayerData = {};
let battlePlayerFoodConsumable = {};
let battleDuration;
let battleRunCount;
let now_battle_map;
let enhancementLevel;
let currentEnhancingIndex = 1;
let enhancementData = {
[currentEnhancingIndex]: { "强化数据": {}, "其他数据": {} }
};
let currentPlayerID = null;
let currentPlayerName = null;
let item_icon_url
let marketData = JSON.parse(localStorage.getItem('MWITools_marketAPI_json'));
const init_Client_Data = localStorage.getItem('initClientData');
if (!init_Client_Data) return;
let init_Client_Data_;
try {
init_Client_Data_ = JSON.parse(init_Client_Data);
} catch (error) {
console.error('数据解析失败:', error);
return;
}
if (init_Client_Data_.type !== 'init_client_data') return;
const item_hrid_to_name = init_Client_Data_.itemDetailMap;
for (const key in item_hrid_to_name) {
if (item_hrid_to_name[key] && typeof item_hrid_to_name[key] === 'object' && item_hrid_to_name[key].name) {
item_hrid_to_name[key] = item_hrid_to_name[key].name;
}
}
const item_name_to_hrid = Object.fromEntries(
Object.entries(item_hrid_to_name).map(([key, value]) => [value, key])
);
const e2c = {
"Coin": "金币",
"Task Token": "任务代币",
"Chimerical Token": "奇幻代币",
"Sinister Token": "阴森代币",
"Enchanted Token": "秘法代币",
"Pirate Token": "海盗代币",
"Cowbell": "牛铃",
"Bag Of 10 Cowbells": "牛铃袋 (10个)",
"Purple's Gift": "小紫牛的礼物",
"Small Meteorite Cache": "小陨石舱",
"Medium Meteorite Cache": "中陨石舱",
"Large Meteorite Cache": "大陨石舱",
"Small Artisan's Crate": "小工匠匣",
"Medium Artisan's Crate": "中工匠匣",
"Large Artisan's Crate": "大工匠匣",
"Small Treasure Chest": "小宝箱",
"Medium Treasure Chest": "中宝箱",
"Large Treasure Chest": "大宝箱",
"Chimerical Chest": "奇幻宝箱",
"Sinister Chest": "阴森宝箱",
"Enchanted Chest": "秘法宝箱",
"Pirate Chest": "海盗宝箱",
"Blue Key Fragment": "蓝色钥匙碎片",
"Green Key Fragment": "绿色钥匙碎片",
"Purple Key Fragment": "紫色钥匙碎片",
"White Key Fragment": "白色钥匙碎片",
"Orange Key Fragment": "橙色钥匙碎片",
"Brown Key Fragment": "棕色钥匙碎片",
"Stone Key Fragment": "石头钥匙碎片",
"Dark Key Fragment": "黑暗钥匙碎片",
"Burning Key Fragment": "燃烧钥匙碎片",
"Chimerical Entry Key": "奇幻钥匙",
"Chimerical Chest Key": "奇幻宝箱钥匙",
"Sinister Entry Key": "阴森钥匙",
"Sinister Chest Key": "阴森宝箱钥匙",
"Enchanted Entry Key": "秘法钥匙",
"Enchanted Chest Key": "秘法宝箱钥匙",
"Pirate Entry Key": "海盗钥匙",
"Pirate Chest Key": "海盗宝箱钥匙",
"Donut": "甜甜圈",
"Blueberry Donut": "蓝莓甜甜圈",
"Blackberry Donut": "黑莓甜甜圈",
"Strawberry Donut": "草莓甜甜圈",
"Mooberry Donut": "哞莓甜甜圈",
"Marsberry Donut": "火星莓甜甜圈",
"Spaceberry Donut": "太空莓甜甜圈",
"Cupcake": "纸杯蛋糕",
"Blueberry Cake": "蓝莓蛋糕",
"Blackberry Cake": "黑莓蛋糕",
"Strawberry Cake": "草莓蛋糕",
"Mooberry Cake": "哞莓蛋糕",
"Marsberry Cake": "火星莓蛋糕",
"Spaceberry Cake": "太空莓蛋糕",
"Gummy": "软糖",
"Apple Gummy": "苹果软糖",
"Orange Gummy": "橙子软糖",
"Plum Gummy": "李子软糖",
"Peach Gummy": "桃子软糖",
"Dragon Fruit Gummy": "火龙果软糖",
"Star Fruit Gummy": "杨桃软糖",
"Yogurt": "酸奶",
"Apple Yogurt": "苹果酸奶",
"Orange Yogurt": "橙子酸奶",
"Plum Yogurt": "李子酸奶",
"Peach Yogurt": "桃子酸奶",
"Dragon Fruit Yogurt": "火龙果酸奶",
"Star Fruit Yogurt": "杨桃酸奶",
"Milking Tea": "挤奶茶",
"Foraging Tea": "采摘茶",
"Woodcutting Tea": "伐木茶",
"Cooking Tea": "烹饪茶",
"Brewing Tea": "冲泡茶",
"Alchemy Tea": "炼金茶",
"Enhancing Tea": "强化茶",
"Cheesesmithing Tea": "奶酪锻造茶",
"Crafting Tea": "制作茶",
"Tailoring Tea": "缝纫茶",
"Super Milking Tea": "超级挤奶茶",
"Super Foraging Tea": "超级采摘茶",
"Super Woodcutting Tea": "超级伐木茶",
"Super Cooking Tea": "超级烹饪茶",
"Super Brewing Tea": "超级冲泡茶",
"Super Alchemy Tea": "超级炼金茶",
"Super Enhancing Tea": "超级强化茶",
"Super Cheesesmithing Tea": "超级奶酪锻造茶",
"Super Crafting Tea": "超级制作茶",
"Super Tailoring Tea": "超级缝纫茶",
"Ultra Milking Tea": "究极挤奶茶",
"Ultra Foraging Tea": "究极采摘茶",
"Ultra Woodcutting Tea": "究极伐木茶",
"Ultra Cooking Tea": "究极烹饪茶",
"Ultra Brewing Tea": "究极冲泡茶",
"Ultra Alchemy Tea": "究极炼金茶",
"Ultra Enhancing Tea": "究极强化茶",
"Ultra Cheesesmithing Tea": "究极奶酪锻造茶",
"Ultra Crafting Tea": "究极制作茶",
"Ultra Tailoring Tea": "究极缝纫茶",
"Gathering Tea": "采集茶",
"Gourmet Tea": "美食茶",
"Wisdom Tea": "经验茶",
"Processing Tea": "加工茶",
"Efficiency Tea": "效率茶",
"Artisan Tea": "工匠茶",
"Catalytic Tea": "催化茶",
"Blessed Tea": "福气茶",
"Stamina Coffee": "耐力咖啡",
"Intelligence Coffee": "智力咖啡",
"Defense Coffee": "防御咖啡",
"Attack Coffee": "攻击咖啡",
"Power Coffee": "力量咖啡",
"Ranged Coffee": "远程咖啡",
"Magic Coffee": "魔法咖啡",
"Super Stamina Coffee": "超级耐力咖啡",
"Super Intelligence Coffee": "超级智力咖啡",
"Super Defense Coffee": "超级防御咖啡",
"Super Attack Coffee": "超级攻击咖啡",
"Super Power Coffee": "超级力量咖啡",
"Super Ranged Coffee": "超级远程咖啡",
"Super Magic Coffee": "超级魔法咖啡",
"Ultra Stamina Coffee": "究极耐力咖啡",
"Ultra Intelligence Coffee": "究极智力咖啡",
"Ultra Defense Coffee": "究极防御咖啡",
"Ultra Attack Coffee": "究极攻击咖啡",
"Ultra Power Coffee": "究极力量咖啡",
"Ultra Ranged Coffee": "究极远程咖啡",
"Ultra Magic Coffee": "究极魔法咖啡",
"Wisdom Coffee": "经验咖啡",
"Lucky Coffee": "幸运咖啡",
"Swiftness Coffee": "迅捷咖啡",
"Channeling Coffee": "吟唱咖啡",
"Critical Coffee": "暴击咖啡",
"Poke": "破胆之刺",
"Impale": "透骨之刺",
"Puncture": "破甲之刺",
"Penetrating Strike": "贯心之刺",
"Scratch": "爪影斩",
"Cleave": "分裂斩",
"Maim": "血刃斩",
"Crippling Slash": "致残斩",
"Smack": "重碾",
"Sweep": "重扫",
"Stunning Blow": "重锤",
"Fracturing Impact": "碎裂冲击",
"Shield Bash": "盾击",
"Quick Shot": "快速射击",
"Aqua Arrow": "流水箭",
"Flame Arrow": "烈焰箭",
"Rain Of Arrows": "箭雨",
"Silencing Shot": "沉默之箭",
"Steady Shot": "稳定射击",
"Pestilent Shot": "疫病射击",
"Penetrating Shot": "贯穿射击",
"Water Strike": "流水冲击",
"Ice Spear": "冰枪术",
"Frost Surge": "冰霜爆裂",
"Mana Spring": "法力喷泉",
"Entangle": "缠绕",
"Toxic Pollen": "剧毒粉尘",
"Nature's Veil": "自然菌幕",
"Life Drain": "生命吸取",
"Fireball": "火球",
"Flame Blast": "熔岩爆裂",
"Firestorm": "火焰风暴",
"Smoke Burst": "烟爆灭影",
"Minor Heal": "初级自愈术",
"Heal": "自愈术",
"Quick Aid": "快速治疗术",
"Rejuvenate": "群体治疗术",
"Taunt": "嘲讽",
"Provoke": "挑衅",
"Toughness": "坚韧",
"Elusiveness": "闪避",
"Precision": "精确",
"Berserk": "狂暴",
"Elemental Affinity": "元素增幅",
"Frenzy": "狂速",
"Spike Shell": "尖刺防护",
"Arcane Reflection": "奥术反射",
"Vampirism": "吸血",
"Revive": "复活",
"Insanity": "疯狂",
"Invincible": "无敌",
"Fierce Aura": "物理光环",
"Aqua Aura": "流水光环",
"Sylvan Aura": "自然光环",
"Flame Aura": "火焰光环",
"Speed Aura": "速度光环",
"Critical Aura": "暴击光环",
"Gobo Stabber": "哥布林长剑",
"Gobo Slasher": "哥布林关刀",
"Gobo Smasher": "哥布林狼牙棒",
"Spiked Bulwark": "尖刺重盾",
"Werewolf Slasher": "狼人关刀",
"Griffin Bulwark": "狮鹫重盾",
"Gobo Shooter": "哥布林弹弓",
"Vampiric Bow": "吸血弓",
"Cursed Bow": "咒怨之弓",
"Gobo Boomstick": "哥布林火棍",
"Cheese Bulwark": "奶酪重盾",
"Verdant Bulwark": "翠绿重盾",
"Azure Bulwark": "蔚蓝重盾",
"Burble Bulwark": "深紫重盾",
"Crimson Bulwark": "绛红重盾",
"Rainbow Bulwark": "彩虹重盾",
"Holy Bulwark": "神圣重盾",
"Wooden Bow": "木弓",
"Birch Bow": "桦木弓",
"Cedar Bow": "雪松弓",
"Purpleheart Bow": "紫心弓",
"Ginkgo Bow": "银杏弓",
"Redwood Bow": "红杉弓",
"Arcane Bow": "神秘弓",
"Stalactite Spear": "石钟长枪",
"Granite Bludgeon": "花岗岩大棒",
"Furious Spear": "狂怒长枪",
"Regal Sword": "君王之剑",
"Chaotic Flail": "混沌连枷",
"Soul Hunter Crossbow": "灵魂猎手弩",
"Sundering Crossbow": "裂空之弩",
"Frost Staff": "冰霜法杖",
"Infernal Battlestaff": "炼狱法杖",
"Jackalope Staff": "鹿角兔之杖",
"Rippling Trident": "涟漪三叉戟",
"Blooming Trident": "绽放三叉戟",
"Blazing Trident": "炽焰三叉戟",
"Cheese Sword": "奶酪剑",
"Verdant Sword": "翠绿剑",
"Azure Sword": "蔚蓝剑",
"Burble Sword": "深紫剑",
"Crimson Sword": "绛红剑",
"Rainbow Sword": "彩虹剑",
"Holy Sword": "神圣剑",
"Cheese Spear": "奶酪长枪",
"Verdant Spear": "翠绿长枪",
"Azure Spear": "蔚蓝长枪",
"Burble Spear": "深紫长枪",
"Crimson Spear": "绛红长枪",
"Rainbow Spear": "彩虹长枪",
"Holy Spear": "神圣长枪",
"Cheese Mace": "奶酪钉头锤",
"Verdant Mace": "翠绿钉头锤",
"Azure Mace": "蔚蓝钉头锤",
"Burble Mace": "深紫钉头锤",
"Crimson Mace": "绛红钉头锤",
"Rainbow Mace": "彩虹钉头锤",
"Holy Mace": "神圣钉头锤",
"Wooden Crossbow": "木弩",
"Birch Crossbow": "桦木弩",
"Cedar Crossbow": "雪松弩",
"Purpleheart Crossbow": "紫心弩",
"Ginkgo Crossbow": "银杏弩",
"Redwood Crossbow": "红杉弩",
"Arcane Crossbow": "神秘弩",
"Wooden Water Staff": "木制水法杖",
"Birch Water Staff": "桦木水法杖",
"Cedar Water Staff": "雪松水法杖",
"Purpleheart Water Staff": "紫心水法杖",
"Ginkgo Water Staff": "银杏水法杖",
"Redwood Water Staff": "红杉水法杖",
"Arcane Water Staff": "神秘水法杖",
"Wooden Nature Staff": "木制自然法杖",
"Birch Nature Staff": "桦木自然法杖",
"Cedar Nature Staff": "雪松自然法杖",
"Purpleheart Nature Staff": "紫心自然法杖",
"Ginkgo Nature Staff": "银杏自然法杖",
"Redwood Nature Staff": "红杉自然法杖",
"Arcane Nature Staff": "神秘自然法杖",
"Wooden Fire Staff": "木制火法杖",
"Birch Fire Staff": "桦木火法杖",
"Cedar Fire Staff": "雪松火法杖",
"Purpleheart Fire Staff": "紫心火法杖",
"Ginkgo Fire Staff": "银杏火法杖",
"Redwood Fire Staff": "红杉火法杖",
"Arcane Fire Staff": "神秘火法杖",
"Eye Watch": "掌上监工",
"Snake Fang Dirk": "蛇牙短剑",
"Vision Shield": "视觉盾",
"Gobo Defender": "哥布林防御者",
"Vampire Fang Dirk": "吸血鬼短剑",
"Knight's Aegis": "骑士盾",
"Treant Shield": "树人盾",
"Manticore Shield": "蝎狮盾",
"Tome Of Healing": "治疗之书",
"Tome Of The Elements": "元素之书",
"Watchful Relic": "警戒遗物",
"Bishop's Codex": "主教法典",
"Cheese Buckler": "奶酪圆盾",
"Verdant Buckler": "翠绿圆盾",
"Azure Buckler": "蔚蓝圆盾",
"Burble Buckler": "深紫圆盾",
"Crimson Buckler": "绛红圆盾",
"Rainbow Buckler": "彩虹圆盾",
"Holy Buckler": "神圣圆盾",
"Wooden Shield": "木盾",
"Birch Shield": "桦木盾",
"Cedar Shield": "雪松盾",
"Purpleheart Shield": "紫心盾",
"Ginkgo Shield": "银杏盾",
"Redwood Shield": "红杉盾",
"Arcane Shield": "神秘盾",
"Sinister Cape": "阴森斗篷",
"Chimerical Quiver": "奇幻箭袋",
"Enchanted Cloak": "秘法披风",
"Red Culinary Hat": "红色厨师帽",
"Snail Shell Helmet": "蜗牛壳头盔",
"Vision Helmet": "视觉头盔",
"Fluffy Red Hat": "蓬松红帽子",
"Corsair Helmet": "掠夺者头盔",
"Acrobatic Hood": "杂技师兜帽",
"Magician's Hat": "魔术师帽",
"Cheese Helmet": "奶酪头盔",
"Verdant Helmet": "翠绿头盔",
"Azure Helmet": "蔚蓝头盔",
"Burble Helmet": "深紫头盔",
"Crimson Helmet": "绛红头盔",
"Rainbow Helmet": "彩虹头盔",
"Holy Helmet": "神圣头盔",
"Rough Hood": "粗糙兜帽",
"Reptile Hood": "爬行动物兜帽",
"Gobo Hood": "哥布林兜帽",
"Beast Hood": "野兽兜帽",
"Umbral Hood": "暗影兜帽",
"Cotton Hat": "棉帽",
"Linen Hat": "亚麻帽",
"Bamboo Hat": "竹帽",
"Silk Hat": "丝帽",
"Radiant Hat": "光辉帽",
"Dairyhand's Top": "挤奶工上衣",
"Forager's Top": "采摘者上衣",
"Lumberjack's Top": "伐木工上衣",
"Cheesemaker's Top": "奶酪师上衣",
"Crafter's Top": "工匠上衣",
"Tailor's Top": "裁缝上衣",
"Chef's Top": "厨师上衣",
"Brewer's Top": "饮品师上衣",
"Alchemist's Top": "炼金师上衣",
"Enhancer's Top": "强化师上衣",
"Gator Vest": "鳄鱼马甲",
"Turtle Shell Body": "龟壳胸甲",
"Colossus Plate Body": "巨像胸甲",
"Demonic Plate Body": "恶魔胸甲",
"Anchorbound Plate Body": "锚定胸甲",
"Maelstrom Plate Body": "怒涛胸甲",
"Marine Tunic": "海洋皮衣",
"Revenant Tunic": "亡灵皮衣",
"Griffin Tunic": "狮鹫皮衣",
"Kraken Tunic": "克拉肯皮衣",
"Icy Robe Top": "冰霜袍服",
"Flaming Robe Top": "烈焰袍服",
"Luna Robe Top": "月神袍服",
"Royal Water Robe Top": "皇家水系袍服",
"Royal Nature Robe Top": "皇家自然系袍服",
"Royal Fire Robe Top": "皇家火系袍服",
"Cheese Plate Body": "奶酪胸甲",
"Verdant Plate Body": "翠绿胸甲",
"Azure Plate Body": "蔚蓝胸甲",
"Burble Plate Body": "深紫胸甲",
"Crimson Plate Body": "绛红胸甲",
"Rainbow Plate Body": "彩虹胸甲",
"Holy Plate Body": "神圣胸甲",
"Rough Tunic": "粗糙皮衣",
"Reptile Tunic": "爬行动物皮衣",
"Gobo Tunic": "哥布林皮衣",
"Beast Tunic": "野兽皮衣",
"Umbral Tunic": "暗影皮衣",
"Cotton Robe Top": "棉布袍服",
"Linen Robe Top": "亚麻袍服",
"Bamboo Robe Top": "竹袍服",
"Silk Robe Top": "丝绸袍服",
"Radiant Robe Top": "光辉袍服",
"Dairyhand's Bottoms": "挤奶工下装",
"Forager's Bottoms": "采摘者下装",
"Lumberjack's Bottoms": "伐木工下装",
"Cheesemaker's Bottoms": "奶酪师下装",
"Crafter's Bottoms": "工匠下装",
"Tailor's Bottoms": "裁缝下装",
"Chef's Bottoms": "厨师下装",
"Brewer's Bottoms": "饮品师下装",
"Alchemist's Bottoms": "炼金师下装",
"Enhancer's Bottoms": "强化师下装",
"Turtle Shell Legs": "龟壳腿甲",
"Colossus Plate Legs": "巨像腿甲",
"Demonic Plate Legs": "恶魔腿甲",
"Anchorbound Plate Legs": "锚定腿甲",
"Maelstrom Plate Legs": "怒涛腿甲",
"Marine Chaps": "航海皮裤",
"Revenant Chaps": "亡灵皮裤",
"Griffin Chaps": "狮鹫皮裤",
"Kraken Chaps": "克拉肯皮裤",
"Icy Robe Bottoms": "冰霜袍裙",
"Flaming Robe Bottoms": "烈焰袍裙",
"Luna Robe Bottoms": "月神袍裙",
"Royal Water Robe Bottoms": "皇家水系袍裙",
"Royal Nature Robe Bottoms": "皇家自然系袍裙",
"Royal Fire Robe Bottoms": "皇家火系袍裙",
"Cheese Plate Legs": "奶酪腿甲",
"Verdant Plate Legs": "翠绿腿甲",
"Azure Plate Legs": "蔚蓝腿甲",
"Burble Plate Legs": "深紫腿甲",
"Crimson Plate Legs": "绛红腿甲",
"Rainbow Plate Legs": "彩虹腿甲",
"Holy Plate Legs": "神圣腿甲",
"Rough Chaps": "粗糙皮裤",
"Reptile Chaps": "爬行动物皮裤",
"Gobo Chaps": "哥布林皮裤",
"Beast Chaps": "野兽皮裤",
"Umbral Chaps": "暗影皮裤",
"Cotton Robe Bottoms": "棉袍裙",
"Linen Robe Bottoms": "亚麻袍裙",
"Bamboo Robe Bottoms": "竹袍裙",
"Silk Robe Bottoms": "丝绸袍裙",
"Radiant Robe Bottoms": "光辉袍裙",
"Enchanted Gloves": "附魔手套",
"Pincer Gloves": "蟹钳手套",
"Panda Gloves": "熊猫手套",
"Magnetic Gloves": "磁力手套",
"Dodocamel Gauntlets": "渡渡驼护手",
"Sighted Bracers": "瞄准护腕",
"Marksman Bracers": "神射护腕",
"Chrono Gloves": "时空手套",
"Cheese Gauntlets": "奶酪护手",
"Verdant Gauntlets": "翠绿护手",
"Azure Gauntlets": "蔚蓝护手",
"Burble Gauntlets": "深紫护手",
"Crimson Gauntlets": "绛红护手",
"Rainbow Gauntlets": "彩虹护手",
"Holy Gauntlets": "神圣护手",
"Rough Bracers": "粗糙护腕",
"Reptile Bracers": "爬行动物护腕",
"Gobo Bracers": "哥布林护腕",
"Beast Bracers": "野兽护腕",
"Umbral Bracers": "暗影护腕",
"Cotton Gloves": "棉手套",
"Linen Gloves": "亚麻手套",
"Bamboo Gloves": "竹手套",
"Silk Gloves": "丝手套",
"Radiant Gloves": "光辉手套",
"Collector's Boots": "收藏家靴",
"Shoebill Shoes": "鲸头鹳鞋",
"Black Bear Shoes": "黑熊鞋",
"Grizzly Bear Shoes": "棕熊鞋",
"Polar Bear Shoes": "北极熊鞋",
"Centaur Boots": "半人马靴",
"Sorcerer Boots": "巫师靴",
"Cheese Boots": "奶酪靴",
"Verdant Boots": "翠绿靴",
"Azure Boots": "蔚蓝靴",
"Burble Boots": "深紫靴",
"Crimson Boots": "绛红靴",
"Rainbow Boots": "彩虹靴",
"Holy Boots": "神圣靴",
"Rough Boots": "粗糙靴",
"Reptile Boots": "爬行动物靴",
"Gobo Boots": "哥布林靴",
"Beast Boots": "野兽靴",
"Umbral Boots": "暗影靴",
"Cotton Boots": "棉靴",
"Linen Boots": "亚麻靴",
"Bamboo Boots": "竹靴",
"Silk Boots": "丝靴",
"Radiant Boots": "光辉靴",
"Small Pouch": "小袋子",
"Medium Pouch": "中袋子",
"Large Pouch": "大袋子",
"Giant Pouch": "巨大袋子",
"Gluttonous Pouch": "贪食之袋",
"Guzzling Pouch": "暴饮之囊",
"Necklace Of Efficiency": "效率项链",
"Fighter Necklace": "战士项链",
"Ranger Necklace": "射手项链",
"Wizard Necklace": "巫师项链",
"Necklace Of Wisdom": "经验项链",
"Necklace Of Speed": "速度项链",
"Philosopher's Necklace": "贤者项链",
"Earrings Of Gathering": "采集耳环",
"Earrings Of Essence Find": "精华发现耳环",
"Earrings Of Armor": "护甲耳环",
"Earrings Of Regeneration": "恢复耳环",
"Earrings Of Resistance": "抗性耳环",
"Earrings Of Rare Find": "稀有发现耳环",
"Earrings Of Critical Strike": "暴击耳环",
"Philosopher's Earrings": "贤者耳环",
"Ring Of Gathering": "采集戒指",
"Ring Of Essence Find": "精华发现戒指",
"Ring Of Armor": "护甲戒指",
"Ring Of Regeneration": "恢复戒指",
"Ring Of Resistance": "抗性戒指",
"Ring Of Rare Find": "稀有发现戒指",
"Ring Of Critical Strike": "暴击戒指",
"Philosopher's Ring": "贤者戒指",
"Basic Task Badge": "基础任务徽章",
"Advanced Task Badge": "高级任务徽章",
"Expert Task Badge": "专家任务徽章",
"Celestial Brush": "星空刷子",
"Cheese Brush": "奶酪刷子",
"Verdant Brush": "翠绿刷子",
"Azure Brush": "蔚蓝刷子",
"Burble Brush": "深紫刷子",
"Crimson Brush": "绛红刷子",
"Rainbow Brush": "彩虹刷子",
"Holy Brush": "神圣刷子",
"Celestial Shears": "星空剪刀",
"Cheese Shears": "奶酪剪刀",
"Verdant Shears": "翠绿剪刀",
"Azure Shears": "蔚蓝剪刀",
"Burble Shears": "深紫剪刀",
"Crimson Shears": "绛红剪刀",
"Rainbow Shears": "彩虹剪刀",
"Holy Shears": "神圣剪刀",
"Celestial Hatchet": "星空斧头",
"Cheese Hatchet": "奶酪斧头",
"Verdant Hatchet": "翠绿斧头",
"Azure Hatchet": "蔚蓝斧头",
"Burble Hatchet": "深紫斧头",
"Crimson Hatchet": "绛红斧头",
"Rainbow Hatchet": "彩虹斧头",
"Holy Hatchet": "神圣斧头",
"Celestial Hammer": "星空锤子",
"Cheese Hammer": "奶酪锤子",
"Verdant Hammer": "翠绿锤子",
"Azure Hammer": "蔚蓝锤子",
"Burble Hammer": "深紫锤子",
"Crimson Hammer": "绛红锤子",
"Rainbow Hammer": "彩虹锤子",
"Holy Hammer": "神圣锤子",
"Celestial Chisel": "星空凿子",
"Cheese Chisel": "奶酪凿子",
"Verdant Chisel": "翠绿凿子",
"Azure Chisel": "蔚蓝凿子",
"Burble Chisel": "深紫凿子",
"Crimson Chisel": "绛红凿子",
"Rainbow Chisel": "彩虹凿子",
"Holy Chisel": "神圣凿子",
"Celestial Needle": "星空针",
"Cheese Needle": "奶酪针",
"Verdant Needle": "翠绿针",
"Azure Needle": "蔚蓝针",
"Burble Needle": "深紫针",
"Crimson Needle": "绛红针",
"Rainbow Needle": "彩虹针",
"Holy Needle": "神圣针",
"Celestial Spatula": "星空锅铲",
"Cheese Spatula": "奶酪锅铲",
"Verdant Spatula": "翠绿锅铲",
"Azure Spatula": "蔚蓝锅铲",
"Burble Spatula": "深紫锅铲",
"Crimson Spatula": "绛红锅铲",
"Rainbow Spatula": "彩虹锅铲",
"Holy Spatula": "神圣锅铲",
"Celestial Pot": "星空壶",
"Cheese Pot": "奶酪壶",
"Verdant Pot": "翠绿壶",
"Azure Pot": "蔚蓝壶",
"Burble Pot": "深紫壶",
"Crimson Pot": "绛红壶",
"Rainbow Pot": "彩虹壶",
"Holy Pot": "神圣壶",
"Celestial Alembic": "星空蒸馏器",
"Cheese Alembic": "奶酪蒸馏器",
"Verdant Alembic": "翠绿蒸馏器",
"Azure Alembic": "蔚蓝蒸馏器",
"Burble Alembic": "深紫蒸馏器",
"Crimson Alembic": "绛红蒸馏器",
"Rainbow Alembic": "彩虹蒸馏器",
"Holy Alembic": "神圣蒸馏器",
"Celestial Enhancer": "星空强化器",
"Cheese Enhancer": "奶酪强化器",
"Verdant Enhancer": "翠绿强化器",
"Azure Enhancer": "蔚蓝强化器",
"Burble Enhancer": "深紫强化器",
"Crimson Enhancer": "绛红强化器",
"Rainbow Enhancer": "彩虹强化器",
"Holy Enhancer": "神圣强化器",
"Milk": "牛奶",
"Verdant Milk": "翠绿牛奶",
"Azure Milk": "蔚蓝牛奶",
"Burble Milk": "深紫牛奶",
"Crimson Milk": "绛红牛奶",
"Rainbow Milk": "彩虹牛奶",
"Holy Milk": "神圣牛奶",
"Cheese": "奶酪",
"Verdant Cheese": "翠绿奶酪",
"Azure Cheese": "蔚蓝奶酪",
"Burble Cheese": "深紫奶酪",
"Crimson Cheese": "绛红奶酪",
"Rainbow Cheese": "彩虹奶酪",
"Holy Cheese": "神圣奶酪",
"Log": "原木",
"Birch Log": "白桦原木",
"Cedar Log": "雪松原木",
"Purpleheart Log": "紫心原木",
"Ginkgo Log": "银杏原木",
"Redwood Log": "红杉原木",
"Arcane Log": "神秘原木",
"Lumber": "木板",
"Birch Lumber": "白桦木板",
"Cedar Lumber": "雪松木板",
"Purpleheart Lumber": "紫心木板",
"Ginkgo Lumber": "银杏木板",
"Redwood Lumber": "红杉木板",
"Arcane Lumber": "神秘木板",
"Rough Hide": "粗糙兽皮",
"Reptile Hide": "爬行动物皮",
"Gobo Hide": "哥布林皮",
"Beast Hide": "野兽皮",
"Umbral Hide": "暗影皮",
"Rough Leather": "粗糙皮革",
"Reptile Leather": "爬行动物皮革",
"Gobo Leather": "哥布林皮革",
"Beast Leather": "野兽皮革",
"Umbral Leather": "暗影皮革",
"Cotton": "棉花",
"Flax": "亚麻",
"Bamboo Branch": "竹子",
"Cocoon": "蚕茧",
"Radiant Fiber": "光辉纤维",
"Cotton Fabric": "棉花布料",
"Linen Fabric": "亚麻布料",
"Bamboo Fabric": "竹子布料",
"Silk Fabric": "丝绸",
"Radiant Fabric": "光辉布料",
"Egg": "鸡蛋",
"Wheat": "小麦",
"Sugar": "糖",
"Blueberry": "蓝莓",
"Blackberry": "黑莓",
"Strawberry": "草莓",
"Mooberry": "哞莓",
"Marsberry": "火星莓",
"Spaceberry": "太空莓",
"Apple": "苹果",
"Orange": "橙子",
"Plum": "李子",
"Peach": "桃子",
"Dragon Fruit": "火龙果",
"Star Fruit": "杨桃",
"Arabica Coffee Bean": "低级咖啡豆",
"Robusta Coffee Bean": "中级咖啡豆",
"Liberica Coffee Bean": "高级咖啡豆",
"Excelsa Coffee Bean": "特级咖啡豆",
"Fieriosa Coffee Bean": "火山咖啡豆",
"Spacia Coffee Bean": "太空咖啡豆",
"Green Tea Leaf": "绿茶叶",
"Black Tea Leaf": "黑茶叶",
"Burble Tea Leaf": "紫茶叶",
"Moolong Tea Leaf": "哞龙茶叶",
"Red Tea Leaf": "红茶叶",
"Emp Tea Leaf": "虚空茶叶",
"Catalyst Of Coinification": "点金催化剂",
"Catalyst Of Decomposition": "分解催化剂",
"Catalyst Of Transmutation": "转化催化剂",
"Prime Catalyst": "至高催化剂",
"Snake Fang": "蛇牙",
"Shoebill Feather": "鲸头鹳羽毛",
"Snail Shell": "蜗牛壳",
"Crab Pincer": "蟹钳",
"Turtle Shell": "乌龟壳",
"Marine Scale": "海洋鳞片",
"Treant Bark": "树皮",
"Centaur Hoof": "半人马蹄",
"Luna Wing": "月神翼",
"Gobo Rag": "哥布林抹布",
"Goggles": "护目镜",
"Magnifying Glass": "放大镜",
"Eye Of The Watcher": "观察者之眼",
"Icy Cloth": "冰霜织物",
"Flaming Cloth": "烈焰织物",
"Sorcerer's Sole": "魔法师鞋底",
"Chrono Sphere": "时空球",
"Frost Sphere": "冰霜球",
"Panda Fluff": "熊猫绒",
"Black Bear Fluff": "黑熊绒",
"Grizzly Bear Fluff": "棕熊绒",
"Polar Bear Fluff": "北极熊绒",
"Red Panda Fluff": "小熊猫绒",
"Magnet": "磁铁",
"Stalactite Shard": "钟乳石碎片",
"Living Granite": "花岗岩",
"Colossus Core": "巨像核心",
"Vampire Fang": "吸血鬼之牙",
"Werewolf Claw": "狼人之爪",
"Revenant Anima": "亡者之魂",
"Soul Fragment": "灵魂碎片",
"Infernal Ember": "地狱余烬",
"Demonic Core": "恶魔核心",
"Griffin Leather": "狮鹫之皮",
"Manticore Sting": "蝎狮之刺",
"Jackalope Antler": "鹿角兔之角",
"Dodocamel Plume": "渡渡驼之翎",
"Griffin Talon": "狮鹫之爪",
"Acrobat's Ribbon": "杂技师彩带",
"Magician's Cloth": "魔术师织物",
"Chaotic Chain": "混沌锁链",
"Cursed Ball": "诅咒之球",
"Royal Cloth": "皇家织物",
"Knight's Ingot": "骑士之锭",
"Bishop's Scroll": "主教卷轴",
"Regal Jewel": "君王宝石",
"Sundering Jewel": "裂空宝石",
"Marksman Brooch": "神射胸针",
"Corsair Crest": "掠夺者徽章",
"Damaged Anchor": "破损船锚",
"Maelstrom Plating": "怒涛甲片",
"Kraken Leather": "克拉肯皮革",
"Kraken Fang": "克拉肯之牙",
"Butter Of Proficiency": "精通之油",
"Thread Of Expertise": "专精之线",
"Branch Of Insight": "洞察之枝",
"Gluttonous Energy": "贪食能量",
"Guzzling Energy": "暴饮能量",
"Milking Essence": "挤奶精华",
"Foraging Essence": "采摘精华",
"Woodcutting Essence": "伐木精华",
"Cheesesmithing Essence": "奶酪锻造精华",
"Crafting Essence": "制作精华",
"Tailoring Essence": "缝纫精华",
"Cooking Essence": "烹饪精华",
"Brewing Essence": "冲泡精华",
"Alchemy Essence": "炼金精华",
"Enhancing Essence": "强化精华",
"Swamp Essence": "沼泽精华",
"Aqua Essence": "海洋精华",
"Jungle Essence": "丛林精华",
"Gobo Essence": "哥布林精华",
"Eyessence": "眼精华",
"Sorcerer Essence": "法师精华",
"Bear Essence": "熊熊精华",
"Golem Essence": "魔像精华",
"Twilight Essence": "暮光精华",
"Abyssal Essence": "地狱精华",
"Chimerical Essence": "奇幻精华",
"Sinister Essence": "阴森精华",
"Enchanted Essence": "秘法精华",
"Pirate Essence": "海盗精华",
"Task Crystal": "任务水晶",
"Star Fragment": "星光碎片",
"Pearl": "珍珠",
"Amber": "琥珀",
"Garnet": "石榴石",
"Jade": "翡翠",
"Amethyst": "紫水晶",
"Moonstone": "月亮石",
"Sunstone": "太阳石",
"Philosopher's Stone": "贤者之石",
"Crushed Pearl": "珍珠碎片",
"Crushed Amber": "琥珀碎片",
"Crushed Garnet": "石榴石碎片",
"Crushed Jade": "翡翠碎片",
"Crushed Amethyst": "紫水晶碎片",
"Crushed Moonstone": "月亮石碎片",
"Crushed Sunstone": "太阳石碎片",
"Crushed Philosopher's Stone": "贤者之石碎片",
"Shard Of Protection": "保护碎片",
"Mirror Of Protection": "保护之镜"
}
let specialItemPrices = {
'Coin': { ask: 1, bid: 1 }, // 默认的特殊物品价值,包括 ask 和 bid 价值
'Cowbell': {
ask: getSpecialItemPrice('Bag Of 10 Cowbells', 'ask') / 10 || 32000,
bid: getSpecialItemPrice('Bag Of 10 Cowbells', 'bid') / 10 || 30000
},
'Chimerical Token': {
ask: getSpecialItemPrice('Chimerical Essence', 'ask') || 600,
bid: getSpecialItemPrice('Chimerical Essence', 'bid') || 600
},
'Sinister Token': {
ask: getSpecialItemPrice('Sinister Essence', 'ask') || 1300,
bid: getSpecialItemPrice('Sinister Essence', 'bid') || 1300
},
'Enchanted Token': {
ask: getSpecialItemPrice('Enchanted Essence', 'ask') || 1800,
bid: getSpecialItemPrice('Enchanted Essence', 'bid') || 1700
},
'Pirate Token': {
ask: getSpecialItemPrice('Pirate Essence', 'ask') || 4000,
bid: getSpecialItemPrice('Pirate Essence', 'bid') || 4000
},
};
const auraAbilities = new Set([
'revive',
'insanity',
'invincible',
'fierce_aura',
'aqua_aura',
'sylvan_aura',
'flame_aura',
'speed_aura',
'critical_aura'
]);
//公会部分代码
const updataDealy = 24*60*60*1000; //数据更新时限
let rateXPDayMap = {};
async function fetchMarketData() {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: MARKET_API_URL,
responseType: 'json',
timeout: 5000,
onload: function(response) {
if (response.status === 200) {
const data = JSON.parse(response.responseText);
console.log('从API获取到的数据:', data);
resolve(data);
} else {
console.error('获取数据失败。状态码:', response.status);
reject(new Error('数据获取失败'));
}
},
ontimeout: function() {
console.error('请求超时:超过5秒未能获取到数据');
reject(new Error('请求超时'));
},
onerror: function(error) {
console.error('获取数据时发生错误:', error);
reject(error);
}
});
});
}
hookWS();
initObserver();
try {
// 尝试从 API 获取数据
marketData = await fetchMarketData();
marketData.market.Coin.ask = 1;
marketData.market.Coin.bid = 1;
} catch (error) {
console.error('从 API 获取数据失败,尝试从本地存储获取数据。', error);
// 从本地存储获取数据
const marketDataStr = localStorage.getItem('MWITools_marketAPI_json');
marketData = JSON.parse(marketDataStr);
if (!marketData) {
alert('无法获取 market 数据');
} else {
console.log('从本地存储获取到的数据:', marketData);
}
}
function getSpecialItemPrice(itemName, priceType) {
if (marketData?.market?.[itemName]) {
const itemPrice = marketData.market[itemName][priceType];
if (itemPrice !== undefined && itemPrice !== -1) {
return itemPrice;
}
}
console.error(`未找到物品 ${itemName} 的 ${priceType} 价格信息`);
return null;
}
function getItemNameFromElement(element) {
const itemNameRaw = element.getAttribute('href').split('#').pop();
return formatItemName(itemNameRaw);
}
function formatItemName(itemNameRaw) {
return item_hrid_to_name[`/items/${itemNameRaw}`]
}
function formatPrice(value,n = 1) {
const isNegative = value < 0;
value = Math.abs(value);
if (value >= 1e13 / n) {
return (isNegative ? '-' : '') + (value / 1e12).toFixed(1) + 'T';
} else if (value >= 1e10 / n) {
return (isNegative ? '-' : '') + (value / 1e9).toFixed(1) + 'B';
} else if (value >= 1e7 / n) {
return (isNegative ? '-' : '') + (value / 1e6).toFixed(1) + 'M';
} else if (value >= 1e4 / n) {
return (isNegative ? '-' : '') + (value / 1e3).toFixed(1) + 'K';
} else {
return (isNegative ? '-' : '') + value.toFixed(0);
}
}
function parseQuantityString(quantityStr) {
const suffix = quantityStr.slice(-1);
const base = parseFloat(quantityStr.slice(0, -1));
if (suffix === 'K') {
return base * 1000;
} else if (suffix === 'M') {
return base * 1000000;
} else if (suffix === 'B') {
return base * 1000000000;
} else {
return parseFloat(quantityStr);
}
}
function recordChestOpening(modalElement) {
if (document.querySelector('.ChestStatistics')) {
return;
}
// 从本地存储读取数据
let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
edibleTools.Chest_Open_Data = edibleTools.Chest_Open_Data || {};
// 确保当前玩家的开箱数据结构存在
if (!currentPlayerID || !currentPlayerName) {
console.error("无法获取当前玩家的 ID 或昵称");
return;
}
edibleTools.Chest_Open_Data[currentPlayerID] = edibleTools.Chest_Open_Data[currentPlayerID] || {
玩家昵称: currentPlayerName,
开箱数据: {}
};
let chestOpenData = edibleTools.Chest_Open_Data[currentPlayerID].开箱数据;
const chestDropData = edibleTools.Chest_Drop_Data;
const chestNameElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_iconContainer__5z7j4 > svg > use");
const chestCountElement = modalElement.querySelector("div.Modal_modal__1Jiep > div.Inventory_modalContent__3ObSx > div.Item_itemContainer__x7kH1 > div > div > div.Item_count__1HVvv");
if (chestNameElement && chestCountElement) {
const chestName = getItemNameFromElement(chestNameElement);
chestOpenData[chestName] = chestOpenData[chestName] || {};
let chestData = chestOpenData[chestName];
const chestCount = parseQuantityString(chestCountElement.textContent.trim());
chestData["总计开箱数量"] = (chestData["总计开箱数量"] || 0) + chestCount;
chestData["获得物品"] = chestData["获得物品"] || {};
const itemsContainer = modalElement.querySelector('.Inventory_gainedItems___e9t9');
const itemElements = itemsContainer.querySelectorAll('.Item_itemContainer__x7kH1');
let totalAskValue = 0;
let totalBidValue = 0;
itemElements.forEach(itemElement => {
const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
if (itemNameElement && itemQuantityElement) {
const itemName = getItemNameFromElement(itemNameElement);
const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
const itemData = chestDropData[chestName].item[itemName] || {};
const itemAskValue = itemData["出售单价"] || 0;
const itemBidValue = itemData["收购单价"] || 0;
const color = itemData.Color || '';
itemQuantityElement.style.color = color;
const itemOpenTotalAskValue = itemAskValue * itemQuantity;
const itemOpenTotalBidValue = itemBidValue * itemQuantity;
chestData["获得物品"][itemName] = chestData["获得物品"][itemName] || {};
chestData["获得物品"][itemName]["数量"] = (chestData["获得物品"][itemName]["数量"] || 0) + itemQuantity;
chestData["获得物品"][itemName]["总计Ask价值"] = (chestData["获得物品"][itemName]["总计Ask价值"] || 0) + itemOpenTotalAskValue;
chestData["获得物品"][itemName]["总计Bid价值"] = (chestData["获得物品"][itemName]["总计Bid价值"] || 0) + itemOpenTotalBidValue;
totalAskValue += itemOpenTotalAskValue;
totalBidValue += itemOpenTotalBidValue;
}
});
chestData["总计开箱Ask"] = (chestData["总计开箱Ask"] || 0) + totalAskValue;
chestData["总计开箱Bid"] = (chestData["总计开箱Bid"] || 0) + totalBidValue;
// 计算本次开箱的偏差值
const differenceValue = totalBidValue - chestDropData[chestName]["期望产出Bid"] * chestCount;
// 更新累计偏差值
chestData["累计偏差值"] = (chestData["累计偏差值"] || 0) + differenceValue;
// 地牢开箱
let profitRange = null;
let profitColor = 'lime'; // 默认颜色
const chestCosts = {
"Chimerical Chest": {
keyAsk: getSpecialItemPrice('Chimerical Chest Key', 'ask') || 3000e3,
keyBid: getSpecialItemPrice('Chimerical Chest Key', 'bid') || 3000e3,
entryAsk: getSpecialItemPrice('Chimerical Entry Key', 'ask') || 280e3,
entryBid: getSpecialItemPrice('Chimerical Entry Key', 'bid') || 280e3
},
"Sinister Chest": {
keyAsk: getSpecialItemPrice('Sinister Chest Key', 'ask') || 6000e3,
keyBid: getSpecialItemPrice('Sinister Chest Key', 'bid') || 5400e3,
entryAsk: getSpecialItemPrice('Sinister Entry Key', 'ask') || 300e3,
entryBid: getSpecialItemPrice('Sinister Entry Key', 'bid') || 280e3
},
"Enchanted Chest": {
keyAsk: getSpecialItemPrice('Enchanted Chest Key', 'ask') || 7600e3,
keyBid: getSpecialItemPrice('Enchanted Chest Key', 'bid') || 7200e3,
entryAsk: getSpecialItemPrice('Enchanted Entry Key', 'ask') || 360e3,
entryBid: getSpecialItemPrice('Enchanted Entry Key', 'bid') || 360e3
},
"Pirate Chest": {
keyAsk: getSpecialItemPrice('Pirate Chest Key', 'ask') || 10000e3,
keyBid: getSpecialItemPrice('Pirate Chest Key', 'bid') || 10000e3,
entryAsk: getSpecialItemPrice('Pirate Entry Key', 'ask') || 460e3,
entryBid: getSpecialItemPrice('Pirate Entry Key', 'bid') || 440e3
}
};
if (chestCosts[chestName]) {
const { keyAsk, keyBid, entryAsk, entryBid } = chestCosts[chestName];
const minProfit = totalBidValue - (keyAsk + entryAsk) * chestCount;
const maxProfit = totalAskValue - (keyBid + entryBid) * chestCount;
profitRange = `${formatPrice(minProfit)}~${formatPrice(maxProfit)}`;
chestData["总计最高利润"] = (chestData["总计最高利润"] || 0) + maxProfit;
chestData["总计最低利润"] = (chestData["总计最低利润"] || 0) + minProfit;
if (minProfit > 0 && maxProfit > 0) {
profitColor = 'lime';
} else if (minProfit < 0 && maxProfit < 0) {
profitColor = 'red';
} else {
profitColor = 'orange';
}
}
// 显示
const openChestElement = document.querySelector('.Inventory_modalContent__3ObSx');
const displayElement = document.createElement('div');
displayElement.classList.add('ChestStatistics');
displayElement.style.position = 'absolute';
displayElement.style.left = `${openChestElement.offsetLeft}px`;
displayElement.style.top = `${openChestElement.offsetTop}px`;
displayElement.style.fontSize = '12px';
displayElement.innerHTML = `
总计开箱次数:<br>
${chestData["总计开箱数量"]}<br>
本次开箱价值:<br>
${formatPrice(totalAskValue)}/${formatPrice(totalBidValue)}<br>
总计开箱价值:<br>
${formatPrice(chestData["总计开箱Ask"])}/${formatPrice(chestData["总计开箱Bid"])}<br>
`;
const expectedOutputElement = document.createElement('div');
expectedOutputElement.classList.add('ExpectedOutput');
expectedOutputElement.style.position = 'absolute';
expectedOutputElement.style.left = `${openChestElement.offsetLeft}px`;
expectedOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
expectedOutputElement.style.fontSize = '12px';
expectedOutputElement.innerHTML = `
预计产出价值:<br>
${formatPrice(chestDropData[chestName]["期望产出Ask"]*chestCount)}/${formatPrice(chestDropData[chestName]["期望产出Bid"]*chestCount)}<br>
`;
const differenceOutputElement = document.createElement('div');
differenceOutputElement.classList.add('DifferenceOutput');
differenceOutputElement.style.position = 'absolute';
differenceOutputElement.style.right = `${openChestElement.offsetLeft}px`;
differenceOutputElement.style.bottom = `${openChestElement.offsetTop}px`;
differenceOutputElement.style.fontSize = '12px';
differenceOutputElement.style.color = differenceValue > 0 ? 'lime' : 'red';
differenceOutputElement.innerHTML = `
${differenceValue > 0 ? '高于期望价值:' : '低于期望价值:'}<br>
${formatPrice(Math.abs(differenceValue))}<br>
`;
// 创建并显示累计偏差值的元素
const cumulativeDifferenceElement = document.createElement('div');
cumulativeDifferenceElement.classList.add('CumulativeDifference');
cumulativeDifferenceElement.style.position = 'absolute';
cumulativeDifferenceElement.style.right = `${openChestElement.offsetLeft}px`;
cumulativeDifferenceElement.style.top = `${openChestElement.offsetTop}px`;
cumulativeDifferenceElement.style.fontSize = '12px';
cumulativeDifferenceElement.style.color = chestData["累计偏差值"] > 0 ? 'lime' : 'red';
cumulativeDifferenceElement.innerHTML = `
<br><br>
<span style="color: ${profitColor};">本次开箱利润</span><br>
${profitRange ? `<span style="color: ${profitColor};">${profitRange}</span>` : `<span style="color: ${profitColor};">${formatPrice(totalAskValue)}/${formatPrice(totalBidValue)}</span>`}<br>
累计${chestData["累计偏差值"] > 0 ? '高于期望:' : '低于期望:'}<br>
${formatPrice(Math.abs(chestData["累计偏差值"]))}<br>
`;
openChestElement.appendChild(displayElement);
openChestElement.appendChild(expectedOutputElement);
openChestElement.appendChild(differenceOutputElement);
openChestElement.appendChild(cumulativeDifferenceElement);
// 保存更新的数据到本地存储
localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
}
}
function calculateTotalValues(itemElements) {
let totalAskValue = 0;
let totalBidValue = 0;
itemElements.forEach(itemElement => {
const itemNameElement = itemElement.querySelector('.Item_iconContainer__5z7j4 use');
const itemQuantityElement = itemElement.querySelector('.Item_count__1HVvv');
if (itemNameElement && itemQuantityElement) {
const itemName = getItemNameFromElement(itemNameElement);
const itemQuantity = parseQuantityString(itemQuantityElement.textContent.trim());
let askPrice = 0;
let bidPrice = 0;
let priceColor = '';
// 获取价格
if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
askPrice = parseFloat(specialItemPrices[itemName].ask);
bidPrice = parseFloat(specialItemPrices[itemName].bid);
priceColor = '';
} else if (marketData?.market?.[itemName]) {
bidPrice = marketData.market[itemName].bid;
askPrice = marketData.market[itemName].ask;
} else {
console.log(`${itemName} 的价格未找到`);
}
const itemTotalAskValue = askPrice * itemQuantity;
const itemTotalBidValue = bidPrice * itemQuantity;
totalAskValue += itemTotalAskValue;
totalBidValue += itemTotalBidValue;
}
});
//console.log(totalAskValue);
return { totalAskValue, totalBidValue };
}
//更详细的战斗等级显示
const updateCombatLevel = () => {
const elements = document.querySelectorAll(".NavigationBar_currentExperience__3GDeX");
if (elements.length === 17) {
const levels = Array.from(elements).slice(10, 17).map(el => {
const levelText = parseInt(el.parentNode.parentNode.querySelector(".NavigationBar_textContainer__7TdaI .NavigationBar_level__3C7eR").textContent);
const decimalPart = parseFloat(el.style.width) / 100;
return { integerPart: levelText, decimalPart: decimalPart };
});
let [endurance, intelligence, attack, strength, defense, ranged, magic] = levels;
let combatTypeMax = Math.max(
0.5 * (attack.integerPart + strength.integerPart),
ranged.integerPart,
magic.integerPart
);
if (combatTypeMax !== 0.5 * (attack.integerPart + strength.integerPart)) {
attack.decimalPart = 0;
strength.decimalPart = 0;
}
if (combatTypeMax !== ranged.integerPart) ranged.decimalPart = 0;
if (combatTypeMax !== magic.integerPart) magic.decimalPart = 0;
let combatLevel = 0.2 * (endurance.integerPart + intelligence.integerPart + defense.integerPart) + 0.4 * combatTypeMax;
combatLevel = parseFloat(combatLevel.toFixed(2));
//console.log("combatLevel",combatLevel)
const integerPart = Math.floor(combatLevel);
const decimalPart = combatLevel - integerPart;
//console.log("integerPart",integerPart)
const list1 = [
endurance.decimalPart * 0.2,
intelligence.decimalPart * 0.2,
attack.decimalPart * 0.2,
strength.decimalPart * 0.2,
defense.decimalPart * 0.2,
ranged.decimalPart * 0.2,
magic.decimalPart * 0.2
];
const list2 = [
endurance.decimalPart * 0.2,
intelligence.decimalPart * 0.2,
attack.decimalPart * 0.2,
strength.decimalPart * 0.2,
defense.decimalPart * 0.2,
ranged.decimalPart * 0.2,
magic.decimalPart * 0.2,
ranged.decimalPart * 0.2,
magic.decimalPart * 0.2
];
//console.log("list1",list1,"\nlist2",list2)
list1.sort((a, b) => b - a);
list2.sort((a, b) => b - a);
if (decimalPart === 0.8) {
combatLevel += list1[0];
} else {
let total = 0;
const maxIterations = Math.floor((1 - decimalPart) / 0.2);
let iterations = 0;
for (const i of list2) {
if (iterations >= maxIterations) break;
if ((decimalPart + total + i) < 1) {
total += i;
} else {
break;
}
iterations++;
}
combatLevel = decimalPart + integerPart + total;
}
elements[15].parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".NavigationBar_nav__3uuUl .NavigationBar_level__3C7eR").textContent = combatLevel.toFixed(2);
}
};
window.setInterval(updateCombatLevel, 10000);
function OfflineStatistics(modalElement) {
const itemsContainer = modalElement.querySelectorAll(".OfflineProgressModal_itemList__26h-Y");
let timeContainer = null;
let getItemContainer = null;
let spendItemContainer = null;
itemsContainer.forEach(container => {
const labelElement = container.querySelector('.OfflineProgressModal_label__2HwFG');
if (labelElement) {
const textContent = labelElement.textContent.trim();
if (textContent.startsWith("Offline duration") || textContent.startsWith("你离线了") || textContent.startsWith("离线时间")) {
timeContainer = container;
} else if (textContent.startsWith("Items gained") || textContent.startsWith("获得物品:") || textContent.startsWith("获得物品")) {
getItemContainer = container;
} else if (textContent.startsWith("Items consumed") || textContent.startsWith("你消耗了:") || textContent.startsWith("消耗物品")) {
spendItemContainer = container;
}
}
});
let TotalSec = null;
if (timeContainer) {
const textContent = timeContainer.textContent;
const match = textContent.match(/(?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s)/);
if (match) {
let days = parseInt(match[1], 10) || 0;
let hours = parseInt(match[2], 10) || 0;
let minutes = parseInt(match[3], 10) || 0;
let seconds = parseInt(match[4], 10) || 0;
TotalSec = days * 86400 + hours * 3600 + minutes * 60 + seconds;
}
}
let getitemtotalAskValue = 0;
let getitemtotalBidValue = 0;
if (getItemContainer) {
const getitemElements = getItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
const { totalAskValue, totalBidValue } = calculateTotalValues(getitemElements);
getitemtotalAskValue = totalAskValue;
getitemtotalBidValue = totalBidValue;
}
let spenditemtotalAskValue = 0;
let spenditemtotalBidValue = 0;
if (spendItemContainer) {
const spenditemElements = spendItemContainer.querySelectorAll('.Item_itemContainer__x7kH1');
const { totalAskValue, totalBidValue } = calculateTotalValues(spenditemElements);
spenditemtotalAskValue = totalAskValue;
spenditemtotalBidValue = totalBidValue;
}
if (timeContainer) {
const newElement = document.createElement('span');
newElement.textContent = `利润: ${formatPrice(getitemtotalBidValue - spenditemtotalAskValue,10)} [${formatPrice((getitemtotalBidValue - spenditemtotalAskValue) / (TotalSec / 3600) * 24,10)}/天]`;
newElement.style.color = 'gold';
newElement.style.whiteSpace = 'nowrap';
newElement.style.marginLeft = 'auto';
timeContainer.querySelector(':first-child').appendChild(newElement);
}
if (getItemContainer) {
const newElement = document.createElement('span');
newElement.textContent = `产出:[${formatPrice(getitemtotalAskValue)}/${formatPrice(getitemtotalBidValue)}]`;
newElement.style.float = 'right';
newElement.style.color = 'gold';
newElement.style.whiteSpace = 'nowrap';
getItemContainer.querySelector(':first-child').appendChild(newElement);
}
if (spendItemContainer) {
const newElement = document.createElement('span');
newElement.textContent = `成本:[${formatPrice(spenditemtotalAskValue)}/${formatPrice(spenditemtotalBidValue)}]`;
newElement.style.float = 'right';
newElement.style.color = 'gold';
newElement.style.whiteSpace = 'nowrap';
spendItemContainer.querySelector(':first-child').appendChild(newElement);
}
}
function initObserver() {
// 选择要观察的目标节点
const targetNode = document.body;
// 观察器的配置(需要观察子节点的变化)
const config = { childList: true, subtree: true };
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(mutationsList => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
// 监听到子节点变化
mutation.addedNodes.forEach(addedNode => {
// 检查是否是我们关注的 Modal_modalContainer__3B80m 元素被添加
if (addedNode.classList && addedNode.classList.contains('Modal_modalContainer__3B80m')) {
// Modal_modalContainer__3B80m 元素被添加,执行处理函数
//console.log("箱子被打开")
ShowChestPrice();
recordChestOpening(addedNode);
// 开始监听箱子图标的变化
startIconObserver();
}
if (addedNode.classList && addedNode.classList.contains('OfflineProgressModal_modalContainer__knnk7')) {
OfflineStatistics(addedNode);
console.log("离线报告已创建!")
}
if (addedNode.classList && addedNode.classList.contains('MainPanel_subPanelContainer__1i-H9')) {
if (addedNode.querySelector(".CombatPanel_combatPanel__QylPo")) {
addBattlePlayerFoodButton();
addBattlePlayerLootButton();
} else if (addedNode.querySelector('.EnhancingPanel_enhancingPanel__ysWpV')) {
updateEnhancementUI();
}
}
});
mutation.removedNodes.forEach(removedNode => {
// 检查是否是 Modal_modalContainer__3B80m 元素被移除
if (removedNode.classList && removedNode.classList.contains('Modal_modalContainer__3B80m')) {
// Modal_modalContainer__3B80m 元素被移除,停止监听箱子图标的变化
stopIconObserver();
}
});
}
}
});
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 定义箱子图标变化的观察器
let iconObserver = null;
// 开始监听箱子图标的变化
function startIconObserver() {
const chestNameElem = document.querySelector(chestNameSelector);
if (!chestNameElem) return;
// 创建一个观察器实例来监听图标的变化
iconObserver = new MutationObserver(() => {
// 当箱子图标变化时,执行处理函数
ShowChestPrice();
});
// 配置观察器的选项
const iconConfig = { attributes: true, attributeFilter: ['href'] };
// 以上述配置开始观察箱子图标节点
iconObserver.observe(chestNameElem, iconConfig);
}
// 停止监听箱子图标的变化
function stopIconObserver() {
if (iconObserver) {
iconObserver.disconnect();
iconObserver = null;
}
}
}
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
return handleMessage(message);
}
}
function addStatisticsButton() {
const waitForNavi = () => {
const targetNode = document.querySelector("div.NavigationBar_minorNavigationLinks__dbxh7");
if (targetNode) {
// 创建统计窗口按钮
let statsButton = document.createElement("div");
statsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
statsButton.style.color = "gold";
statsButton.innerHTML = isCN ? "开箱统计" : "Chest Statistics";
statsButton.addEventListener("click", () => {
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
const openChestData = edibleTools.Chest_Open_Data || {};
createVisualizationWindow(openChestData);
});
// 创建食用工具按钮
let edibleToolsButton = document.createElement("div");
edibleToolsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
edibleToolsButton.style.color = "gold";
edibleToolsButton.innerHTML = isCN ? "食用工具" : "Edible Tools";
edibleToolsButton.addEventListener("click", () => {
openSettings();
});
// 将按钮添加到目标节点
targetNode.insertAdjacentElement("afterbegin", statsButton);
targetNode.insertAdjacentElement("afterbegin", edibleToolsButton);
//获取图标url格式模板
item_icon_url = document.querySelector("div[class^='Item_itemContainer'] use")?.getAttribute("href")?.split("#")[0];
addBattlePlayerFoodButton();
addBattlePlayerLootButton();
} else {
setTimeout(waitForNavi, 200);
}
};
waitForNavi(); // 开始等待目标节点出现
}
//奶牛钉钉
function handleMessage(message) {
try {
let obj = JSON.parse(message);
if (obj && obj.type === "new_battle") {
processCombatConsumables(obj);
} else if (obj && obj.type === "init_character_data") {
now_battle_map = undefined;
processCharacterData(obj);
addStatisticsButton();
update_market_list(obj);
} else if (obj && obj.type === "action_completed" && obj.endCharacterAction) {
const actionHrid = obj.endCharacterAction.actionHrid;
if (actionHrid === "/actions/enhancing/enhance") {
processEnhancementData(obj);
} else if (actionHrid.startsWith("/actions/combat/")) {
now_battle_map = actionHrid;
}
} else if (obj && obj.type === "guild_updated") {
const Guild_ID = obj.guild.id;
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
edibleTools.Guild_Data = edibleTools.Guild_Data || {};
let storedData = edibleTools.Guild_Data || {};
// 判断是否已经存在旧数据
if (storedData[Guild_ID] && storedData[Guild_ID].guild_updated && storedData[Guild_ID].guild_updated.old.updatedAt) {
const oldUpdatedAt = new Date(storedData[Guild_ID].guild_updated.new.updatedAt);
const newUpdatedAt = new Date(obj.guild.updatedAt);
// 计算时间差(单位:毫秒)
const timeDifference = newUpdatedAt - oldUpdatedAt;
if (timeDifference >= updataDealy) {
// 更新老数据为新数据
storedData[Guild_ID].guild_updated.old = storedData[Guild_ID].guild_updated.new;
// 更新新数据为当前数据
storedData[Guild_ID].guild_updated.new = {
experience: obj.guild.experience,
level: obj.guild.level,
updatedAt: obj.guild.updatedAt
};
} else {
// 仅更新新数据
storedData[Guild_ID].guild_updated.new = {
experience: obj.guild.experience,
level: obj.guild.level,
updatedAt: obj.guild.updatedAt
};
}
//计算Δ
const Delta = {
Delta_Xp: storedData[Guild_ID].guild_updated.new.experience - storedData[Guild_ID].guild_updated.old.experience,
Delta_Level: storedData[Guild_ID].guild_updated.new.level - storedData[Guild_ID].guild_updated.old.level,
Delta_Time: (newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000, // 转换为秒
Rate_XP_Hours: (3600*(obj.guild.experience - storedData[Guild_ID].guild_updated.old.experience)/((newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000)).toFixed(2)
};
storedData[Guild_ID].guild_updated.Delta = Delta;
const Guild_TotalXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[1];
if (Guild_TotalXp_div) {
const xpText = isCN ? "经验值 / 小时" : "XP / Hour";
Guild_TotalXp_div.insertAdjacentHTML(
"afterend",
`<div>${formatPrice(Delta.Rate_XP_Hours)} ${xpText}</div>`
);
const Guild_NeedXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2];
if (Guild_NeedXp_div) {
const Guild_NeedXp = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2].textContent.replace(/,/g, '');
const Time = TimeReset(Guild_NeedXp/Delta.Rate_XP_Hours);
Guild_NeedXp_div.insertAdjacentHTML(
"afterend", // 使用 "afterend" 在元素的后面插入内容
`<div>${Time}</div>`
);
}
}
} else {
// 如果没有旧数据,则直接添加新数据
storedData[Guild_ID] = {
guild_name: obj.guild.name,
guild_updated: {
old: {
experience: obj.guild.experience,
level: obj.guild.level,
updatedAt: obj.guild.updatedAt
},
new: {},
}
};
}
// 存储更新后的数据到 localStorage
edibleTools.Guild_Data = storedData;
localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
} else if (obj && obj.type === "guild_characters_updated") {
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
edibleTools.Guild_Data = edibleTools.Guild_Data || {};
let storedData = edibleTools.Guild_Data || {};
for (const key in obj.guildSharableCharacterMap) {
if (obj.guildSharableCharacterMap.hasOwnProperty(key)) {
const Guild_ID = obj.guildCharacterMap[key].guildID;
const name = obj.guildSharableCharacterMap[key].name;
const newUpdatedAt = new Date();
storedData[Guild_ID].guild_player = storedData[Guild_ID].guild_player || {};
if (storedData[Guild_ID] && storedData[Guild_ID].guild_player && storedData[Guild_ID].guild_player[name] && storedData[Guild_ID].guild_player[name].old && storedData[Guild_ID].guild_player[name].old.updatedAt) {
const oldUpdatedAt = new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)
const timeDifference = newUpdatedAt - oldUpdatedAt
if (timeDifference >= updataDealy) {
// 更新老数据为新数据
storedData[Guild_ID].guild_player[name].old = storedData[Guild_ID].guild_player[name].new;
// 更新新数据为当前数据
storedData[Guild_ID].guild_player[name].new = {
id: key,
gameMode: obj.guildSharableCharacterMap[key].gameMode,
guildExperience: obj.guildCharacterMap[key].guildExperience,
updatedAt: newUpdatedAt,
};
} else {
// 仅更新新数据
storedData[Guild_ID].guild_player[name].new = {
id: key,
gameMode: obj.guildSharableCharacterMap[key].gameMode,
guildExperience: obj.guildCharacterMap[key].guildExperience,
updatedAt: newUpdatedAt,
};
}
//计算Δ
const Delta = {
Delta_Time:(newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000,
Delta_Xp: storedData[Guild_ID].guild_player[name].new.guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience,
Rate_XP_Day: (24*3600*(obj.guildCharacterMap[key].guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience)/((newUpdatedAt - new Date(storedData[Guild_ID].guild_player[name].old.updatedAt)) / 1000)).toFixed(2)
};
storedData[Guild_ID].guild_player[name].Delta = Delta;
rateXPDayMap[name] = Delta.Rate_XP_Day;
}else {
storedData[Guild_ID].guild_player[name] = {
old: {
id: key,
gameMode: obj.guildSharableCharacterMap[key].gameMode,
guildExperience: obj.guildCharacterMap[key].guildExperience,
updatedAt: newUpdatedAt,
},
new:{}
};
}
}
}
//console.log("测试数据",storedData);
//console.log("guild_characters_updated", obj);
updateExperienceDisplay(rateXPDayMap);
edibleTools.Guild_Data = storedData;
localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
} else if (obj && obj.type === "market_listings_updated") {
update_market_list(obj);
} else if (obj && obj.type === "battle_consumable_ability_updated" && obj.consumable) {
const itemHrid = obj.consumable.itemHrid
battlePlayerFoodConsumable
}
} catch (error) {
console.error("Error processing message:", error);
}
return message;
}
// 订单数据更新
function update_market_list(date) {
if (!date) return;
let market_list = JSON.parse(GM_getValue('market_list', '[]'));
// 通用更新
function updateOrders(orders) {
orders.forEach(newOrder => {
const existingOrderIndex = market_list.findIndex(order => order.id === newOrder.id);
if (existingOrderIndex !== -1) {
market_list[existingOrderIndex] = newOrder;
} else {
market_list.push(newOrder);
}
// 给每个订单添加更新时间戳
newOrder.lastUpdated = new Date().toISOString();
});
}
// 更新市场数据
if (date.type === "init_character_data" && date.myMarketListings) {
updateOrders(date.myMarketListings);
} else if (date.type === "market_listings_updated" && date.endMarketListings) {
updateOrders(date.endMarketListings);
}
// 保存更新后的数据
GM_setValue('market_list', JSON.stringify(market_list));
}
function TimeReset(hours) {
const totalMinutes = hours * 60;
const days = Math.floor(totalMinutes / (24 * 60));
const yudays = totalMinutes % (24 * 60);
const hrs = Math.floor(yudays / 60);
const minutes = Math.floor(yudays % 60);
const dtext = isCN ? "天" : "d";
const htext = isCN ? "时" : "h";
const mtext = isCN ? "分" : "m";
return `${days}${dtext} ${hrs}${htext} ${minutes}${mtext}`;
}
function updateExperienceDisplay(rateXPDayMap) {
const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr");
const idleuser_list = [];
const dtext = isCN ? "天" : "d";
// 将 rateXPDayMap 转换为数组并排序
const sortedMembers = Object.entries(rateXPDayMap)
.map(([name, XPdata]) => ({ name, XPdata }))
.sort((a, b) => b.XPdata - a.XPdata);
sortedMembers.forEach(({ name, XPdata }) => {
trElements.forEach(tr => {
const nameElement = tr.querySelector(".CharacterName_name__1amXp");
const experienceElement = tr.querySelector("td:nth-child(3) > div");
const activityElement = tr.querySelector('.GuildPanel_activity__9vshh');
if (nameElement && nameElement.textContent.trim() === name) {
if (activityElement.childElementCount === 0) {
idleuser_list.push(nameElement.textContent.trim());
}
if (experienceElement) {
const newDiv = document.createElement('div');
newDiv.textContent = `${formatPrice(XPdata)}/${dtext}`;
// 计算颜色
const rank = sortedMembers.findIndex(member => member.name === name);
const hue = 120 - (rank * (120 / (sortedMembers.length - 1)));
newDiv.style.color = `hsl(${hue}, 100%, 50%)`;
experienceElement.insertAdjacentElement('afterend', newDiv);
}
return;
}
});
});
update_idleuser_tb(idleuser_list);
}
function update_idleuser_tb(idleuser_list) {
const targetElement = document.querySelector('.GuildPanel_noticeMessage__3Txji');
if (!targetElement) {
console.error('公会标语元素未找到!');
return;
}
const clonedElement = targetElement.cloneNode(true);
const namesText = idleuser_list.join(', ');
clonedElement.innerHTML = '';
clonedElement.textContent = isCN ? `闲置的成员:${namesText}` : `Idle User : ${namesText}`;
clonedElement.style.color = '#ffcc00';
// 设置复制元素的高度为原元素的25%
const originalStyle = window.getComputedStyle(targetElement);
const originalHeight = originalStyle.height;
const originalMinHeight = originalStyle.minHeight;
clonedElement.style.height = `25%`;
clonedElement.style.minHeight = `25%`; // 也设置最小高度
targetElement.parentElement.appendChild(clonedElement);
}
//箱子数据获取
function processCharacterData(init_character_data) {
const initClientData = localStorage.getItem('initClientData');
if (!initClientData) return;
let init_client_data
try {
init_client_data = JSON.parse(initClientData);
} catch (error) {
console.error('数据解析失败:', error);
return;
}
if (init_client_data.type !== 'init_client_data') return;
const item_hrid_to_name = init_client_data.itemDetailMap;
for (const key in item_hrid_to_name) {
if (item_hrid_to_name[key] && typeof item_hrid_to_name[key] === 'object' && item_hrid_to_name[key].name) {
item_hrid_to_name[key] = item_hrid_to_name[key].name;
}
}
const item_name_to_hrid = Object.fromEntries(
Object.entries(item_hrid_to_name).map(([key, value]) => [value, key])
);
const hrid2name = item_hrid_to_name;
const character = init_character_data.character;
if (character) {
currentPlayerID = character.id;
currentPlayerName = character.name;
}
let formattedShopData = {};
if (init_character_data?.characterActions[0]?.actionHrid?.startsWith("/actions/combat/")) {now_battle_map = init_character_data.characterActions[0].actionHrid}
// 处理商店数据
for (let [key, details] of Object.entries(init_client_data.shopItemDetailMap)) {
const { itemHrid, costs } = details;
const itemName = hrid2name[itemHrid] || formatItemName(itemHrid.split('/').pop());
costs.forEach(cost => {
const costItemName = hrid2name[cost.itemHrid] || formatItemName(cost.itemHrid.split('/').pop());
if (costItemName === "Coin") return;
const costCount = cost.count;
if (!formattedShopData[costItemName]) {
formattedShopData[costItemName] = { items: {}, 最挣钱: '', BID单价: 0 };
}
// 计算每种代币购买每个物品的收益
let bidValue = getSpecialItemPrice(itemName,"bid") || 0;
let profit = bidValue / costCount;
formattedShopData[costItemName].items[itemName] = {
花费: costCount
};
// 更新最赚钱的物品信息
if (profit > formattedShopData[costItemName].BID单价) {
formattedShopData[costItemName].最挣钱 = itemName;
formattedShopData[costItemName].BID单价 = profit;
(specialItemPrices[costItemName] ??= {}).ask = profit;
(specialItemPrices[costItemName] ??= {}).bid = profit;
}
});
}
const mostProfitableItems = Object.values(formattedShopData).map(item => item.最挣钱).filter(Boolean);
//console.log(mostProfitableItems)
// 处理箱子掉落物数据
for (let iteration = 0; iteration < 4; iteration++) {
for (let [key, items] of Object.entries(init_client_data.openableLootDropMap)) {
const boxName = hrid2name[key] || formatItemName(key.split('/').pop());
if (!formattedChestDropData[boxName]) {
formattedChestDropData[boxName] = { item: {} };
}
let TotalAsk = 0;
let TotalBid = 0;
let awa = 0;
items.forEach(item => {
const { itemHrid, dropRate, minCount, maxCount } = item;
const itemName = hrid2name[itemHrid] || formatItemName(itemHrid.split('/').pop());
const expectedYield = ((minCount + maxCount) / 2) * dropRate;
let bidPrice = -1;
let askPrice = -1;
let priceColor = '';
if (specialItemPrices[itemName] && specialItemPrices[itemName].ask) {
askPrice = parseFloat(specialItemPrices[itemName].ask);
bidPrice = parseFloat(specialItemPrices[itemName].bid);
priceColor = '';
} else if (marketData?.market?.[itemName]) {
bidPrice = marketData.market[itemName].bid;
askPrice = marketData.market[itemName].ask;
} else {
console.log(`${itemName} 的价格未找到`);
}
if (formattedChestDropData[boxName].item[itemName] && iteration === 0) {
// 如果物品已存在,更新期望掉落和相关价格
const existingItem = formattedChestDropData[boxName].item[itemName];
existingItem.期望掉落 += expectedYield;
} else if (iteration === 0) {
formattedChestDropData[boxName].item[itemName] = {
期望掉落: expectedYield,
};
}
// 判断 itemName 是否在最挣钱物品列表中
if (mostProfitableItems.includes(itemName)) {
priceColor = '#FFb3E6';
} else if (askPrice === -1 && bidPrice === -1) {
priceColor = 'yellow';
} else if (askPrice === -1) {
askPrice = bidPrice;
priceColor = '#D95961';
} else if (bidPrice === -1) {
priceColor = '#2FC4A7';
}
const existingItem = formattedChestDropData[boxName].item[itemName];
existingItem.出售单价 = askPrice;
existingItem.收购单价 = bidPrice;
existingItem.出售总价 = (existingItem.出售单价 * existingItem.期望掉落).toFixed(2);
existingItem.收购总价 = (existingItem.收购单价 * existingItem.期望掉落).toFixed(2);
existingItem.Color = priceColor;
// 累计总价
TotalAsk += (askPrice * expectedYield);
TotalBid += (bidPrice * expectedYield);
});
formattedChestDropData[boxName] = {
...formattedChestDropData[boxName],
期望产出Ask: TotalAsk.toFixed(2),
期望产出Bid: TotalBid.toFixed(2),
};
if (!specialItemPrices[boxName]) {
specialItemPrices[boxName] = {}
}
specialItemPrices[boxName].ask = formattedChestDropData[boxName].期望产出Ask;
specialItemPrices[boxName].bid = formattedChestDropData[boxName].期望产出Bid;
}
}
for (let itemName in specialItemPrices) {
if (specialItemPrices.hasOwnProperty(itemName)) {
marketData.market[itemName] = {
ask: specialItemPrices[itemName].ask,
bid: specialItemPrices[itemName].bid
};
}
}
localStorage.setItem('MWITools_marketAPI_json', JSON.stringify(marketData));
//处理战斗地图数据
const combatMaps = {};
const actionDetailMap = init_client_data.actionDetailMap;
for (const [actionHrid, actionDetail] of Object.entries(actionDetailMap)) {
if (!actionHrid.startsWith("/actions/combat/")) continue;
if (!actionDetail.combatZoneInfo || actionDetail.combatZoneInfo.isDungeon) continue;
const fightInfo = actionDetail.combatZoneInfo.fightInfo;
const randomSpawnInfo = fightInfo?.randomSpawnInfo;
const spawns = randomSpawnInfo?.spawns;
if (!spawns || spawns.length === 0) continue;
// 确定地图类型
let mapType = "群战";
if (spawns.length === 1) {
mapType = "单怪";
}
if (actionHrid.includes("_elite") && mapType === "群战") {
mapType = "群战(精英)";
}
const monsterGen = {};
const maxSpawnCount = randomSpawnInfo.maxSpawnCount;
const maxTotalStrength = randomSpawnInfo.maxTotalStrength;
const expectedCounts = calculateExpectedSpawns(spawns, maxSpawnCount, maxTotalStrength);
spawns.forEach(spawn => {
monsterGen[spawn.combatMonsterHrid] = {
期望数量: expectedCounts[spawn.combatMonsterHrid],
精英等级: spawn.eliteTier
};
});
// 处理BOSS数据
const bossData = {};
const bossSpawns = fightInfo.bossSpawns;
const battlesPerBoss = fightInfo.battlesPerBoss;
if (bossSpawns && bossSpawns.length > 0) {
bossSpawns.forEach(boss => {
if (boss.combatMonsterHrid) {
bossData[boss.combatMonsterHrid] = {
精英等级: boss.eliteTier
};
}
});
}
combatMaps[actionHrid] = {
地图类型: mapType,
BOSS波次: battlesPerBoss || 0,
小怪生成: monsterGen,
BOSS数据: Object.keys(bossData).length > 0 ? bossData : ""
};
}
const combatMobDropData = {};
const monsterMap = init_client_data.combatMonsterDetailMap;
for (const [monsterHrid, monsterData] of Object.entries(monsterMap)) {
const formattedDrops = {
怪物名称: monsterData.name,
普通掉落: [],
稀有掉落: []
};
// 处理普通掉落表
if (monsterData.dropTable) {
monsterData.dropTable.forEach(drop => {
formattedDrops.普通掉落.push({
掉落物名称: item_hrid_to_name[drop.itemHrid] || drop.itemHrid,
掉落物Hrid: drop.itemHrid,
掉落几率: drop.dropRate,
掉落数量: (drop.minCount + drop.maxCount) / 2,
精英门槛: drop.minEliteTier
});
});
}
// 处理稀有掉落表
if (monsterData.rareDropTable) {
monsterData.rareDropTable.forEach(drop => {
formattedDrops.稀有掉落.push({
掉落物名称: item_hrid_to_name[drop.itemHrid] || drop.itemHrid,
掉落物Hrid: drop.itemHrid,
掉落几率: drop.dropRate,
掉落数量: (drop.minCount + drop.maxCount) / 2,
精英门槛: drop.minEliteTier
});
});
}
combatMobDropData[monsterHrid] = formattedDrops;
}
let edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
edibleTools = {
...edibleTools,
Chest_Drop_Data: formattedChestDropData,
Combat_Data: { Combat_Map_Data: combatMaps ,Combat_Mob_Drop_Data: combatMobDropData}
};
edibleTools.Chest_Open_Data = edibleTools.Chest_Open_Data || {};
if (edibleTools.Chest_Open_Data && !edibleTools.Chest_Open_Data[0]) {
const oldData = { ...edibleTools.Chest_Open_Data };
edibleTools.Chest_Open_Data = {};
edibleTools.Chest_Open_Data[0] = {
玩家昵称: "老版本开箱数据",
开箱数据: oldData
};
}
edibleTools.Chest_Open_Data[currentPlayerID] = edibleTools.Chest_Open_Data[currentPlayerID] || {
玩家昵称: currentPlayerName,
开箱数据: {}
};
try {
localStorage.setItem('Edible_Tools', JSON.stringify(edibleTools));
} catch (error) {
console.error('保存数据时发生错误:', error);
}
// 打印结果
//console.log("特殊物品价格表:",specialItemPrices)
//console.log("箱子掉落物列表:", formattedChestDropData);
//console.log("地牢商店列表:", formattedShopData);
//console.log("战斗地图列表",combatMaps)
//console.log("怪物掉落列表",combatMobDropData)
}
function calculateExpectedSpawns(spawns, maxSpawnCount, maxTotalStrength) {
const monsterList = spawns.map(s => ({ hrid: s.combatMonsterHrid, strength: s.strength }));
const spawnProbability = 1 / spawns.length;
const dp = Array.from({ length: maxSpawnCount + 1 }, () => ({}));
dp[0][0] = 1;
const expectedCounts = {};
monsterList.forEach(m => {
expectedCounts[m.hrid] = 0;
});
for (let pos = 0; pos < maxSpawnCount; pos++) {
const currentDP = dp[pos];
const nextDP = dp[pos + 1] = {};
for (const [currentStrengthStr, prob] of Object.entries(currentDP)) {
const currentStrength = parseInt(currentStrengthStr);
for (const monster of monsterList) {
const newStrength = currentStrength + monster.strength;
if (newStrength > maxTotalStrength) continue;
const transitionProb = prob * spawnProbability;
nextDP[newStrength] = (nextDP[newStrength] || 0) + transitionProb;
expectedCounts[monster.hrid] += transitionProb;
}
}
}
return expectedCounts;
}
function ShowChestPrice() {
const modalContainer = document.querySelector(".Modal_modalContainer__3B80m");
if (!modalContainer) return; // 如果不存在 Modal_modalContainer__3B80m 元素,则直接返回
const chestNameElem = document.querySelector(chestNameSelector);
if (!chestNameElem) return;
const chestName = getItemNameFromElement(chestNameElem);
const items = document.querySelectorAll(itemSelector);
const dropListContainer = document.querySelector('.ItemDictionary_openToLoot__1krnv');
if (!dropListContainer) return; // 检查 dropListContainer 是否存在
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools'))
const formattedChestDropData = edibleTools.Chest_Drop_Data;
items.forEach(item => {
const itemName = getItemNameFromElement(item.querySelector(iconSelector));
if (!itemName) return; // 检查 itemName 是否存在
const itemData = formattedChestDropData[chestName].item[itemName];
if (!itemData) return; // 检查 itemData 是否存在
const itemColor = itemData.Color;
const itemNameElem = item.querySelector('.Item_name__2C42x');
if (itemNameElem && itemColor) {
itemNameElem.style.color = itemColor;
}
});
const askPrice = formattedChestDropData[chestName]["期望产出Ask"];
const bidPrice = formattedChestDropData[chestName]["期望产出Bid"];
if (askPrice && bidPrice) {
const previousResults = dropListContainer.querySelectorAll('.resultDiv');
previousResults.forEach(result => result.remove());
const createPriceOutput = (label, price) => {
const priceOutput = document.createElement('div');
priceOutput.className = 'resultDiv';
priceOutput.textContent = `${label}: ${formatPrice(price)}`;
priceOutput.style.color = 'gold';
priceOutput.style.fontSize = '14px';
priceOutput.style.fontWeight = '400';
priceOutput.style.paddingTop = '10px';
return priceOutput;
};
const minPriceOutput = createPriceOutput(isCN ? '期望产出 (最低买入价计算)' : 'Expected Output (Ask Price)', askPrice);
const maxPriceOutput = createPriceOutput(isCN ? '期望产出 (最高收购价计算)' : 'Expected Output (Bid Price)', bidPrice);
dropListContainer.appendChild(minPriceOutput);
dropListContainer.appendChild(maxPriceOutput);
}
}
function processEnhancementData(obj) {
const now_enhancementLevel = parseInt(obj.endCharacterAction.primaryItemHash.match(/::(\d+)$/)[1]);
const currentCount = obj.endCharacterAction.currentCount;
// 开始新的物品的强化
if (enhancementData[currentEnhancingIndex]["强化次数"] && currentCount <= enhancementData[currentEnhancingIndex]["强化次数"]) {
currentEnhancingIndex++;
enhancementData[currentEnhancingIndex] = { "强化数据": {}, "其他数据": {} };
enhancementLevel = undefined;
}
//初始化数据
if (!enhancementData[currentEnhancingIndex]["其他数据"]["物品名称"]) {
const itemName = item_hrid_to_name[obj.endCharacterAction.primaryItemHash.match(/::([^:]+)::[^:]*$/)[1]];
enhancementData[currentEnhancingIndex]["其他数据"] = {
"物品名称": itemName,
"目标强化等级": obj.endCharacterAction.enhancingMaxLevel,
"保护消耗总数": 0,
}
const filteredItems = obj.endCharacterItems.filter(
item => item.hash !== obj.endCharacterAction.primaryItemHash
);
const candidateItems = filteredItems.filter(
item => item.itemHrid === obj.endCharacterAction.primaryItemHash.split('::')[2]
);
let prevLevelItem;
if (candidateItems.length === 1) {
prevLevelItem = candidateItems[0];
} else if (candidateItems.length > 1) {
prevLevelItem = candidateItems.find(
item => item.hash !== obj.endCharacterAction.secondaryItemHash
);
}
enhancementLevel = prevLevelItem?.enhancementLevel ?? 0;
}
//统计强化次数
const currentItem = enhancementData[currentEnhancingIndex]["强化数据"];
if (!currentItem[enhancementLevel]) {
currentItem[enhancementLevel] = {"祝福次数": 0, "成功次数": 0, "失败次数": 0, "成功率": 0 };
}
if (enhancementLevel < now_enhancementLevel) {
currentItem[enhancementLevel]["成功次数"]++;
if (now_enhancementLevel - enhancementLevel == 2) {
currentItem[enhancementLevel]["祝福次数"]++;
}
} else {
currentItem[enhancementLevel]["失败次数"]++;
if (obj.endCharacterAction.enhancingProtectionMinLevel >= 2 && enhancementLevel >= obj.endCharacterAction.enhancingProtectionMinLevel) {
enhancementData[currentEnhancingIndex]["其他数据"]["保护消耗总数"]++;
}
}
const success = currentItem[enhancementLevel]["成功次数"];
const failure = currentItem[enhancementLevel]["失败次数"];
currentItem[enhancementLevel]["成功率"] = success / (success + failure);
// 计算强化状态
const highestSuccessLevel = Math.max(...Object.keys(currentItem).filter(level => currentItem[level]["成功次数"] > 0));
const enhancementState = (highestSuccessLevel + 1 >= enhancementData[currentEnhancingIndex]["其他数据"]["目标强化等级"]) ? "强化成功" : "强化失败";
enhancementData[currentEnhancingIndex]["强化状态"] = enhancementState;
enhancementLevel = now_enhancementLevel;
//console.log(enhancementData)
enhancementData[currentEnhancingIndex]["强化次数"] = currentCount;
updateEnhancementUI();
}
function updateEnhancementUI() {
const targetElement = document.querySelector(".SkillActionDetail_enhancingComponent__17bOx");
if (!targetElement) return;
// 创建父容器
let parentContainer = document.querySelector("#enhancementParentContainer");
if (!parentContainer) {
parentContainer = document.createElement("div");
parentContainer.id = "enhancementParentContainer";
parentContainer.style.display = "block"; // 设置为纵向布局(块级元素)
parentContainer.style.borderLeft = "2px solid var(--color-divider)";
parentContainer.style.padding = "0 4px";
// 创建并添加标题
const title = document.createElement("div");
title.textContent = isCN ? "强化数据" : "Enhancement Data";
title.style.fontWeight = "bold";
title.style.marginBottom = "10px"; // 标题与下拉框之间的间距
title.style.textAlign = "center";
title.style.color = "var(--color-space-300)";
parentContainer.appendChild(title);
// 创建并添加下拉框
const dropdownContainer = document.createElement("div");
dropdownContainer.style.marginBottom = "10px"; // 下拉框与表格之间的间距
const dropdown = document.createElement("select");
dropdown.id = "enhancementDropdown";
dropdown.addEventListener("change", function () {
renderEnhancementUI(this.value);
updateDropdownColor();
});
dropdownContainer.appendChild(dropdown);
parentContainer.appendChild(dropdownContainer);
// 创建并添加表格容器
const enhancementStatsContainer = document.createElement("div");
enhancementStatsContainer.id = "enhancementStatsContainer";
enhancementStatsContainer.style.display = "grid";
enhancementStatsContainer.style.gridTemplateColumns = "repeat(4, 1fr)";
enhancementStatsContainer.style.gap = "10px";
enhancementStatsContainer.style.textAlign = "center";
enhancementStatsContainer.style.marginTop = "10px";
parentContainer.appendChild(enhancementStatsContainer);
targetElement.appendChild(parentContainer);
}
// 更新下拉框内容
const dropdown = document.querySelector("#enhancementDropdown");
const previousSelectedValue = dropdown.value;
dropdown.innerHTML = ""; // 清空下拉框内容
Object.keys(enhancementData).forEach(key => {
const item = enhancementData[key];
const option = document.createElement("option");
const itemName = item["其他数据"]["物品名称"];
const transferName = isCN && e2c[itemName] ? e2c[itemName] : itemName
const targetLevel = item["其他数据"]["目标强化等级"];
const currentLevel = Math.max(...Object.keys(item["强化数据"]));
const enhancementState = item["强化状态"];
option.text = isCN
? `${transferName} (目标: ${targetLevel}, 总计: ${item["强化次数"]}${item["其他数据"]["保护消耗总数"] > 0 ? `, 垫子: ${item["其他数据"]["保护消耗总数"]}` : ""})`
: `${transferName} (Target: ${targetLevel}, Total: ${item["强化次数"]}${item["其他数据"]["保护消耗总数"] > 0 ? `, PU: ${item["其他数据"]["保护消耗总数"]}` : ""})`;
option.value = key;
option.style.color = enhancementState === "强化成功" ? "green"
: (currentLevel < targetLevel && Object.keys(enhancementData).indexOf(key) === Object.keys(enhancementData).length - 1) ? "orange"
: "red";
dropdown.appendChild(option);
});
// 设置默认选中项并渲染表格数据
if (Object.keys(enhancementData).length > 0) {
dropdown.value = previousSelectedValue || Object.keys(enhancementData)[0];
updateDropdownColor();
renderEnhancementUI(dropdown.value);
}
function updateDropdownColor() {
const selectedOption = dropdown.options[dropdown.selectedIndex];
dropdown.style.color = selectedOption ? selectedOption.style.color : "black";
}
}
function renderEnhancementUI(selectedKey) {
const enhancementStatsContainer = document.querySelector("#enhancementStatsContainer");
enhancementStatsContainer.innerHTML = ""; // 清空现有内容
const item = enhancementData[selectedKey];
// 表头
const headers = ["等级", "成功", "失败", "概率"];
headers.forEach(headerText => {
const headerDiv = document.createElement("div");
headerDiv.style.fontWeight = "bold";
headerDiv.textContent = isCN ? headerText : (headerText === "等级" ? "Level" : headerText === "成功" ? "Success" : headerText === "失败" ? "Failure" : "Rate");
enhancementStatsContainer.appendChild(headerDiv);
});
// 总计信息
const totalSuccess = Object.values(item["强化数据"]).reduce((acc, val) => acc + val["成功次数"], 0);
const totalFailure = Object.values(item["强化数据"]).reduce((acc, val) => acc + val["失败次数"], 0);
const totalCount = totalSuccess + totalFailure;
const totalRate = totalCount > 0 ? (totalSuccess / totalCount * 100).toFixed(2) : "0.00";
// 将总计信息添加到表格中
["总计", totalSuccess, totalFailure, `${totalRate}%`].forEach((totalText, index) => {
const totalDiv = document.createElement("div");
totalDiv.textContent = isCN ? totalText : index === 0 ? "Total" : totalText;
enhancementStatsContainer.appendChild(totalDiv);
});
// 渲染各个强化等级的数据
Object.keys(item["强化数据"]).sort((a, b) => b - a).forEach(level => {
const levelData = item["强化数据"][level];
const levelDivs = [
level,
levelData["祝福次数"] > 0
? `${levelData["成功次数"]}(${levelData["祝福次数"]})`
: `${levelData["成功次数"]}`,
levelData["失败次数"],
`${(levelData["成功率"] * 100).toFixed(2)}%`
];
levelDivs.forEach(data => {
const dataDiv = document.createElement("div");
dataDiv.textContent = data;
enhancementStatsContainer.appendChild(dataDiv);
});
});
}
function processCombatConsumables(obj) {
battlePlayerFood = {};
battlePlayerLoot = {};
battlePlayerData = {};
battleDuration = (new Date() - new Date(obj.combatStartTime)) / 1000;
battleRunCount = obj.battleId || 1;
obj.players.forEach(player => {
const playerName = player.character.name;
// 初始化玩家数据
battlePlayerFood[playerName] = { drinkConcentration: player.combatDetails.combatStats.drinkConcentration };
battlePlayerLoot[playerName] = {};
battlePlayerData[playerName] = { aura: null, skillexp: {} ,combatDropQuantity: player.combatDetails.combatStats.combatDropQuantity ,combatDropRate: player.combatDetails.combatStats.combatDropRate, combatRareFind: player.combatDetails.combatStats.combatRareFind};
// 处理消耗品
player.combatConsumables.forEach(consumable => {
const itemname = item_hrid_to_name[consumable.itemHrid];
battlePlayerFood[playerName][itemname] = {
"数量": consumable.count,
"颜色": "white",
"ID": consumable.itemHrid
};
});
// 处理战利品
Object.values(player.totalLootMap).forEach(Loot => {
const itemname = item_hrid_to_name[Loot.itemHrid];
battlePlayerLoot[playerName][itemname] = {
"数量": Loot.count,
"ID": Loot.itemHrid
};
});
// 处理光环
player.combatAbilities.forEach(ability => {
const isAura = Array.from(auraAbilities).some(aura => ability.abilityHrid.endsWith(aura));
if (isAura) {
battlePlayerData[playerName].aura = ability.abilityHrid;
}
});
Object.keys(player.totalSkillExperienceMap).forEach(skillPath => {
const skillname = skillPath.replace('/skills/', '');
battlePlayerData[playerName].skillexp[skillname] = player.totalSkillExperienceMap[skillPath];
});
});
}
function addBattlePlayerFoodButton() {
// 出警按钮父元素路径
var tabsContainer = document.querySelector("#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div > div > div.TabsComponent_tabsContainer__3BDUp > div > div > div")
var referenceTab = tabsContainer ? tabsContainer.children[1] : null;
if (!tabsContainer || !referenceTab) return;
if (tabsContainer.querySelector('.Button_battlePlayerFood__custom')) return;
// 创建按钮
var battlePlayerFoodButton = document.createElement('div');
battlePlayerFoodButton.className = referenceTab.className + ' Button_battlePlayerFood__custom';
battlePlayerFoodButton.setAttribute('script_translatedfrom', 'New Action');
battlePlayerFoodButton.textContent = isCN ? "出警" : "Dispatch";
battlePlayerFoodButton.addEventListener('click', function() {
// 计算最大数量字符长度
let maxQuantityLength = 0;
Object.values(battlePlayerFood).forEach(playerData => {
Object.entries(playerData).forEach(([itemName, itemData]) => {
if (itemName === 'drinkConcentration') return;
const length = formatPrice(itemData.数量).length;
if (length > maxQuantityLength) maxQuantityLength = length;
});
});
// 计算所有玩家的最短剩余时间
let minTimeOverall = Infinity;
let minTimePlayer = null;
Object.keys(battlePlayerFood).forEach(playerName => {
const playerData = battlePlayerFood[playerName];
const drinkConcentration = playerData.drinkConcentration || 0;
let minTime = Infinity;
Object.entries(playerData).forEach(([itemName, itemData]) => {
if (itemName === 'drinkConcentration') return;
let unitTime = itemName.includes('Coffee') ? 300 / (1 + drinkConcentration) : 60;
const totalDays = (itemData.数量 * unitTime) / 86400;
if (totalDays < minTime) minTime = totalDays;
});
if (minTime < minTimeOverall) {
minTimeOverall = minTime;
minTimePlayer = playerName;
}
});
// 弹窗
let dataHtml = `<div style="display: flex; padding: 10px; gap: 15px;">`;
Object.keys(battlePlayerFood).forEach(playerName => {
const playerData = battlePlayerFood[playerName];
const isMinTimePlayer = Object.keys(battlePlayerFood).length > 1 && playerName === minTimePlayer;
dataHtml += `<div style="flex-shrink: 0; padding: 15px; border: 1px solid #98a7e9; border-radius: 10px; background-color: #1e1e2f; white-space: nowrap;">
<h3 style="color: ${isMinTimePlayer ? 'red' : '#98a7e9'}; margin: 0 0 15px 0; text-align: center;">
${playerName}
</h3>`;
// 计算物品时间
const drinkConcentration = playerData.drinkConcentration || 0;
let minTime = Infinity;
const items = [];
Object.entries(playerData).forEach(([itemName, itemData]) => {
if (itemName === 'drinkConcentration') return;
let unitTime = itemName.includes('Coffee') ? 300 / (1 + drinkConcentration) : 60;
const totalDays = (itemData.数量 * unitTime) / 86400;
items.push({ itemName, itemData, totalDays });
if (totalDays < minTime) minTime = totalDays;
});
// 物品显示
items.forEach(({ itemName, itemData, totalDays }) => {
const isMinItem = totalDays === minTime;
const svgIcon = `<svg width="20" height="20" style="margin-right:8px;vertical-align:middle">
<use href="${item_icon_url}#${itemData.ID.split('/').pop()}"></use>
</svg>`;
dataHtml += `
<div style="display: flex; align-items: center; background-color: #2c2e45; border-radius: 5px; padding: 4px; margin-bottom: 8px; border: 1px solid #98a7e9;">
<span style="color: ${isMinItem ? 'red' : 'white'};
min-width: ${maxQuantityLength * 10}px;
text-align: center;">
${formatPrice(itemData.数量)}
</span>
${svgIcon}
<span style="color: ${isMinItem ? 'red' : 'white'};">${isCN && e2c[itemName] ? e2c[itemName] : itemName}</span>
</div>`;
});
// 时间显示
const timeDisplay = minTime < 1
? `${(minTime * 24).toFixed(1)}小时`
: `${minTime.toFixed(1)}天`;
dataHtml += `
<div style="margin-top: 15px; padding-top: 10px; border-top: 1px solid #98a7e9;">
<p style="color: ${isMinTimePlayer ? 'red' : '#4CAF50'}; margin: 0; font-weight: bold; text-align: center;">${isCN ? "剩余时间" : "Duration"}: ${timeDisplay}</p>
</div>`;
// 光环显示
const playerAura = battlePlayerData[playerName]?.aura;
if (playerAura) {
const auraHrid = playerAura.split('/').pop();
const auraItemHrid = `/items/${auraHrid}`;
const auraName = item_hrid_to_name[auraItemHrid] || auraHrid;
const transferAuraName = isCN && e2c[auraName] ? e2c[auraName] : auraName;
dataHtml += `
<div style="margin-top: 10px; text-align: center;">
<p style="color: #98a7e9; margin: 0; font-weight: bold;">${isCN ? "光环" : "Aura"}: ${transferAuraName}</p></div>`;
}
dataHtml += `</div>`;
});
dataHtml += '</div>';
// 弹窗容器
let popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.backgroundColor = '#131419';
popup.style.border = '1px solid #98a7e9';
popup.style.borderRadius = '10px';
popup.style.zIndex = '10000';
popup.style.maxWidth = '90%';
// 水平容器
let scrollWrapper = document.createElement('div');
scrollWrapper.style.overflowX = 'auto';
scrollWrapper.style.padding = '20px';
scrollWrapper.innerHTML = dataHtml;
// 关闭按钮
let closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.display = 'block';
closeButton.style.width = 'calc(100% - 40px)';
closeButton.style.margin = '0 20px 15px';
closeButton.style.backgroundColor = '#4357af';
closeButton.style.color = 'white';
closeButton.style.border = 'none';
closeButton.style.padding = '10px';
closeButton.style.borderRadius = '5px';
closeButton.style.cursor = 'pointer';
closeButton.onclick = () => document.body.removeChild(popup);
popup.appendChild(scrollWrapper);
popup.appendChild(closeButton);
document.body.appendChild(popup);
});
// 插入按钮
var lastTab = tabsContainer.children[tabsContainer.children.length - 1];
tabsContainer.insertBefore(battlePlayerFoodButton, lastTab.nextSibling);
// 按钮样式
var style = document.createElement('style');
style.innerHTML = `
.Button_battlePlayerFood__custom {
background-color: #546ddb;
color: white;
border-radius: 5px;
padding: 5px 10px;
cursor: pointer;
transition: background-color 0.3s;
}
.Button_battlePlayerFood__custom:hover {
background-color: #6b84ff;
}`;
document.head.appendChild(style);
}
function addBattlePlayerLootButton() {
// 获取按钮父元素路径
var tabsContainer = document.querySelector("#root > div > div > div.GamePage_gamePanel__3uNKN > div.GamePage_contentPanel__Zx4FH > div.GamePage_middlePanel__uDts7 > div.GamePage_mainPanel__2njyb > div > div:nth-child(1) > div > div > div > div.TabsComponent_tabsContainer__3BDUp > div > div > div");
var referenceTab = tabsContainer ? tabsContainer.children[1] : null;
if (!tabsContainer || !referenceTab) {
return;
}
// 如果按钮已经存在,直接返回
if (tabsContainer.querySelector('.Button_battlePlayerLoot__custom')) {
console.log('分赃按钮已存在');
return;
}
// 创建按钮
var battlePlayerLootButton = document.createElement('div');
battlePlayerLootButton.className = referenceTab.className + ' Button_battlePlayerLoot__custom';
battlePlayerLootButton.setAttribute('script_translatedfrom', 'New Action');
battlePlayerLootButton.textContent = isCN ? "分赃" : "Loot Distribution";
// 按钮点击事件
battlePlayerLootButton.addEventListener('click', function() {
const isMobile = window.innerWidth < 768; // 判断是否为移动设备
const playerCount = Object.keys(battlePlayerLoot).length;
let maxItemsToShow = 10; // 默认显示10个物品
const EPH = (60 * 60 * (battleRunCount - 1) / battleDuration)
const skillTranslation = {
attack: isCN ? '攻击' : 'Attack',
defense: isCN ? '防御' : 'Defense',
intelligence: isCN ? '智力' : 'Intelligence',
power: isCN ? '力量' : 'Power',
stamina: isCN ? '耐力' : 'Stamina',
magic: isCN ? '魔法' : 'Magic',
ranged: isCN ? '远程' : 'Ranged',
};
if (isMobile) {
if (playerCount === 3) {
maxItemsToShow = 3;
} else if (playerCount === 2) {
maxItemsToShow = 5;
} else if (playerCount === 1) {
maxItemsToShow = 10;
} else if (playerCount > 3) {
maxItemsToShow = 1;
}
}
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
const combatData = edibleTools.Combat_Data || {};
const currentMapData = combatData.Combat_Map_Data?.[now_battle_map];
const combatDropData = combatData.Combat_Mob_Drop_Data || {};
const Mob_Kill_List = {};
if (currentMapData && combatDropData) {
const bossWave = currentMapData.BOSS波次;
if (bossWave === 0) {
Object.entries(currentMapData.小怪生成).forEach(([monsterHrid, data]) => {
Mob_Kill_List[monsterHrid] = {
击杀数量: data.期望数量 * (battleRunCount - 1),
精英等级: data.精英等级
};
});
} else {
const fullCycles = Math.floor((battleRunCount - 1) / bossWave);
const remainingWaves = (battleRunCount - 1) % bossWave;
const normalWaves = fullCycles * (bossWave - 1) + remainingWaves;
Object.entries(currentMapData.小怪生成).forEach(([monsterHrid, data]) => {
Mob_Kill_List[monsterHrid] = {
击杀数量: data.期望数量 * normalWaves,
精英等级: data.精英等级
};
});
if (currentMapData.BOSS数据 && typeof currentMapData.BOSS数据 === 'object') {
Object.entries(currentMapData.BOSS数据).forEach(([bossHrid, bossData]) => {
const existing = Mob_Kill_List[bossHrid] || { 击杀数量: 0 };
Mob_Kill_List[bossHrid] = {
击杀数量: existing.击杀数量 + fullCycles,
精英等级: bossData.精英等级
};
});
}
}
console.log(Mob_Kill_List)
}
let dataHtml = '<div style="display: flex; flex-direction: ' + (isMobile ? 'column' : 'row') + '; flex-wrap: nowrap; background-color: #131419; padding: ' + (isMobile ? '5px' : '10px') + '; border-radius: 10px; color: white;">';
const minPrice = 10000;
// 获取所有玩家的总计价格
let playerPrices = [];
for (let player in battlePlayerLoot) {
let totalPrice = 0;
let lootItems = battlePlayerLoot[player];
for (let item in lootItems) {
let bidPrice = getSpecialItemPrice(item,"bid") || 0;
totalPrice += bidPrice * lootItems[item].数量;
}
playerPrices.push({ player, totalPrice });
}
// 找到眉笔
let minTotalPricePlayer = null;
if (playerPrices.length > 1) {
minTotalPricePlayer = playerPrices.reduce((min, current) =>
current.totalPrice < min.totalPrice ? current : min
).player;
}
// 显示高价值物品
for (let player in battlePlayerLoot) {
const PlayerBonusData = battlePlayerData[player];
const playerExpectDrops = {};
const commonDropRateMultiplier = 1 + (PlayerBonusData.combatDropRate || 0);
const rareDropRateMultiplier = 1 + (PlayerBonusData.combatRareFind || 0);
const dropQuantityMultiplier = 1 + (PlayerBonusData.combatDropQuantity || 0);
for (const [monsterHrid, killInfo] of Object.entries(Mob_Kill_List)) {
const monsterDrops = combatDropData[monsterHrid];
if (!monsterDrops) continue;
const processDrops = (drops, isRare) => {
for (const drop of drops) {
if (killInfo.精英等级 < drop.精英门槛) continue;
const rateMultiplier = isRare ? rareDropRateMultiplier : commonDropRateMultiplier;
const actualRate = Math.min(drop.掉落几率 * rateMultiplier, 1);
const actualQuantity = drop.掉落数量 * dropQuantityMultiplier / playerCount;
const expected = killInfo.击杀数量 * actualRate * actualQuantity;
if (expected > 0) {
const key = drop.掉落物名称;
playerExpectDrops[key] = (playerExpectDrops[key] || 0) + expected;
}
}
}
if (monsterDrops.普通掉落) processDrops(monsterDrops.普通掉落, false);
if (monsterDrops.稀有掉落) processDrops(monsterDrops.稀有掉落, true);
}
let totalExpectPrice = 0;
for (const [itemName, expectedQuantity] of Object.entries(playerExpectDrops)) {
const unitPrice = getSpecialItemPrice(itemName, 'bid');
if (unitPrice !== null) {
totalExpectPrice += unitPrice * expectedQuantity;
}
}
const formattedExpectDrops = {};
for (const [itemHrid, value] of Object.entries(playerExpectDrops)) {
formattedExpectDrops[itemHrid] = Number(value.toFixed(2));
}
console.log(formattedExpectDrops)
//计算食物期望消耗
let totalFoodPrice = 0;
const playerFood = battlePlayerFood[player];
for (let foodName in playerFood) {
if (foodName === 'drinkConcentration') continue;
const foodPrice = getSpecialItemPrice(foodName, 'ask') || 0;
let consumptionRate = 0;
if (foodName.endsWith('Coffee')) {
consumptionRate = 300 / (1 + (playerFood.drinkConcentration || 0));
} else if (foodName.endsWith('Donut') || foodName.endsWith('Cake') || foodName.endsWith('cake')) {
consumptionRate = 75;
} else if (foodName.endsWith('Gummy') || foodName.endsWith('Yogurt')) {
consumptionRate = 67;
}
const totalConsumed = (battleDuration / consumptionRate);
totalFoodPrice += totalConsumed * foodPrice;
}
let totalPrice = 0;
dataHtml += `<div style="flex: 1 0 auto; min-width: 100px; margin: ${isMobile ? '5px 0' : '10px'}; padding: ${isMobile ? '5px' : '10px'}; border-radius: 10px; background-color: #1e1e2f; border: 1px solid #98a7e9;">`;
dataHtml += `<h3 style="color: white; margin: ${isMobile ? '0 0 5px 0' : '0 0 10px 0'}; font-size: ${isMobile ? '12px' : '20px'};">${player}</h3>`;
// 计算总价格
let lootItems = battlePlayerLoot[player];
for (let item in lootItems) {
let bidPrice = getSpecialItemPrice(item,"bid") || 0;
totalPrice += bidPrice * lootItems[item].数量;
}
// 显示总计价格
if (totalPrice > 0 && playerCount <= 3) {
let color = '#4CAF50';
if (player === minTotalPricePlayer) {
color = '#FF0000';
}
// 计算每天价格
const pricePerDay = formatPrice((60 * 60 * 24 * totalPrice) / battleDuration);
const ExpectPricePerDay = formatPrice((60 * 60 * 24 * totalExpectPrice) / battleDuration);
const expectedProfit = totalExpectPrice - totalFoodPrice;
const expectedProfitPerDay = (60 * 60 * 24 * expectedProfit) / battleDuration;
dataHtml += `
<div style="color: ${color}; font-weight: bold; font-size: ${isMobile ? '10px' : '16px'}; margin: ${isMobile ? '2px 0' : '10px 0'};">
<div style="margin-bottom: ${isMobile ? '4px' : '8px'};">
${isCN ? '总计价值' : 'Total Revenue'}: ${formatPrice(totalPrice)}<br>
${isCN ? '每天收入' : 'Daily Revenue'}: ${pricePerDay}/d
</div>
${totalExpectPrice > 0 ? `
<div style="height: 1px; background: #98a7e9; margin: ${isMobile ? '3px 0' : '6px 0'};"></div>
<div style="color: ${totalPrice > totalExpectPrice ? '#4CAF50':'#FF0000'}; margin-bottom: ${isMobile ? '4px' : '8px'};">
${(!isMobile) ? `${isCN ? '期望产值' : 'Expected Revenue'}: ${formatPrice(totalExpectPrice)}<br>` : ''}
${isCN ? '期望日入' : 'NoRNG Daily'}: ${ExpectPricePerDay}/d<br>
${isCN ? '期望日利' : 'Expected Daily'}: ${formatPrice(expectedProfitPerDay)}/d
</div>
` : ''}
</div>`;
}
let maxSkill = null;
let maxXp = 0;
if (battlePlayerData[player]?.skillexp) {
for (let skill in battlePlayerData[player].skillexp) {
let xp = battlePlayerData[player].skillexp[skill];
if (xp > maxXp) {
maxXp = xp;
maxSkill = skill;
}
}
}
const xpPerHours = formatPrice((60 * 60 * maxXp) / battleDuration);
const translatedSkillName = skillTranslation[maxSkill] || maxSkill;
dataHtml += `
<div style="height: 1px; background: #98a7e9; margin: ${isMobile ? '3px 0' : '6px 0'};"></div>
<div style="color: #FFC107; font-size: ${isMobile ? '10px' : '16px'}; font-weight: bold; margin: ${isMobile ? '2px 0' : '10px 0'};">
${isCN ? `${translatedSkillName}经验` : `${translatedSkillName} EXP`}: ${xpPerHours}/h
</div>`;
let sortedItems = Object.keys(lootItems)
.map(item => {
let bidPrice = getSpecialItemPrice(item, "bid") || 0;
return {
item,
bidPrice,
quantity: lootItems[item].数量
};
})
.filter(item => item.bidPrice >= 10000)
.sort((a, b) => b.bidPrice - a.bidPrice);
let maxQuantityLength = Math.max(...sortedItems.map(item => item.quantity.toString().length));
for (let i = 0; i < Math.min(sortedItems.length, maxItemsToShow); i++) {
let item = sortedItems[i].item;
let bidPrice = sortedItems[i].bidPrice;
let quantity = sortedItems[i].quantity;
// 创建图标
let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgIcon.setAttribute('width', isMobile ? '12' : '20');
svgIcon.setAttribute('height', isMobile ? '12' : '20');
svgIcon.style.marginRight = '3px';
svgIcon.style.verticalAlign = 'middle';
let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
useElement.setAttribute('href', `${item_icon_url}#${lootItems[item].ID.split('/').pop()}`);
svgIcon.appendChild(useElement);
// 显示物品数量、图标和名称
dataHtml += `
<div style="display: flex; align-items: center; background-color: #2c2e45; border-radius: 5px; padding: ${isMobile ? '3px' : '8px'}; margin-bottom: ${isMobile ? '3px' : '8px'}; border: 1px solid #98a7e9; white-space: nowrap; flex-shrink: 0;">
<span style="color: white; margin-right: 3px; min-width: ${isMobile ? maxQuantityLength * 5 : maxQuantityLength * 8}px; text-align: center; font-size: ${isMobile ? '10px' : '16px'}; line-height: 1.2;">${quantity}</span>
${svgIcon.outerHTML}
<span style="color: white; white-space: nowrap; font-size: ${isMobile ? '10px' : '16px'}; line-height: 1.2;">${isCN && e2c[item] ? e2c[item] : item}</span>
</div>`;
}
dataHtml += '</div>';
}
dataHtml += '</div>';
// 创建弹窗
let popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.backgroundColor = '#131419';
popup.style.border = '1px solid #98a7e9';
popup.style.padding = isMobile ? '10px 10px 10px' : '20px 20px 20px';
popup.style.borderRadius = '10px';
popup.style.zIndex = '10000';
popup.style.maxWidth = '90%';
popup.style.maxHeight = '90%';
popup.style.overflowX = 'auto';
popup.style.overflowY = 'auto';
popup.style.whiteSpace = 'nowrap';
popup.innerHTML = dataHtml;
const newElement = document.createElement('div');
newElement.textContent = `${EPH.toFixed(1)} EPH`;
newElement.style.position = 'absolute';
newElement.style.top = '0';
newElement.style.left = '50%';
newElement.style.transform = 'translateX(-50%)';
newElement.style.height = isMobile ? '10px' : '20px';
newElement.style.minWidth = isMobile ? '80px' : '160px';
newElement.style.display = 'flex';
newElement.style.alignItems = 'center';
newElement.style.justifyContent = 'center';
newElement.style.backgroundColor = '#4357af';
newElement.style.borderRadius = '0 0 5px 5px';
newElement.style.fontSize = isMobile ? '8px' : '16px';
newElement.style.color = 'white';
newElement.style.fontWeight = 'bold';
newElement.style.lineHeight = '1';
newElement.style.zIndex = '1';
// 添加关闭按钮
let closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.position = 'sticky';
closeButton.style.bottom = '0';
closeButton.style.display = 'block';
closeButton.style.margin = '5px auto 0 auto';
closeButton.style.backgroundColor = '#4357af';
closeButton.style.color = 'white';
closeButton.style.border = 'none';
closeButton.style.padding = isMobile ? '5px 10px' : '10px 20px';
closeButton.style.borderRadius = '5px';
closeButton.style.cursor = 'pointer';
closeButton.style.fontSize = isMobile ? '12px' : '14px';
closeButton.onclick = function() {
document.body.removeChild(popup);
};
popup.appendChild(newElement);
popup.appendChild(closeButton);
document.body.appendChild(popup);
});
// 将按钮插入到最后一个标签后面
var lastTab = tabsContainer.children[tabsContainer.children.length - 1];
tabsContainer.insertBefore(battlePlayerLootButton, lastTab.nextSibling);
// 添加按钮样式
var style = document.createElement('style');
style.innerHTML = `
.Button_battlePlayerLoot__custom {
background-color: #db5454;
color: white;
border-radius: 5px;
padding: 5px 10px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.Button_battlePlayerLoot__custom:hover {
background-color: #ff6b6b;
}
`;
document.head.appendChild(style);
}
//菜单
GM_registerMenuCommand('打印所有箱子掉落物', function() {
console.log('箱子掉落物列表:', formattedChestDropData);
});
function createWindowBase() {
let windowDiv = document.createElement('div');
windowDiv.className = 'visualization-window';
windowDiv.style.position = 'fixed';
windowDiv.style.top = '50%';
windowDiv.style.left = '50%';
windowDiv.style.transform = 'translate(-50%, -50%)';
windowDiv.style.minWidth = '300px';
windowDiv.style.maxWidth = 'min(400px, 90vw)';
windowDiv.style.maxHeight = '80vh';
windowDiv.style.backgroundColor = '#131419';
windowDiv.style.border = '1px solid #98a7e9';
windowDiv.style.borderRadius = '10px';
windowDiv.style.zIndex = '10000';
windowDiv.style.padding = '20px';
windowDiv.style.boxSizing = 'border-box';
windowDiv.style.display = 'flex';
windowDiv.style.flexDirection = 'column';
windowDiv.style.gap = '15px';
windowDiv.style.color = '#ffffff';
windowDiv.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.25)';
windowDiv.style.overflow = 'hidden';
return windowDiv;
}
function createVisualizationWindow(chestData) {
let oldWindow = document.querySelector('.visualization-window');
if (oldWindow) oldWindow.remove();
let windowDiv = createWindowBase();
windowDiv.style.minHeight = '300px';
windowDiv.style.maxWidth = '400px';
// 标题
let title = document.createElement('h1');
title.innerText = isCN ? '选择角色' : 'Select Character';
title.style.color = '#98a7e9';
title.style.margin = '0';
title.style.fontSize = '1.5em';
title.style.textAlign = 'center';
windowDiv.appendChild(title);
// 内容区域
let contentDiv = document.createElement('div');
contentDiv.style.flex = '1';
contentDiv.style.overflowY = 'auto';
contentDiv.style.paddingRight = '8px';
contentDiv.style.display = 'flex';
contentDiv.style.flexDirection = 'column';
contentDiv.style.gap = '10px';
// 玩家列表
for (let playerID in chestData) {
const playerData = chestData[playerID];
const playerName = playerData.玩家昵称;
let playerBox = document.createElement('div');
playerBox.style.display = 'flex';
playerBox.style.alignItems = 'center';
playerBox.style.border = '1px solid #98a7e9';
playerBox.style.borderRadius = '8px';
playerBox.style.padding = '12px';
playerBox.style.cursor = 'pointer';
playerBox.style.backgroundColor = '#1e1e2f';
playerBox.style.transition = 'all 0.3s ease';
// 悬停效果
playerBox.onmouseenter = () => {
playerBox.style.backgroundColor = '#2c2e45';
playerBox.style.transform = 'translateX(5px)';
};
playerBox.onmouseleave = () => {
playerBox.style.backgroundColor = '#1e1e2f';
playerBox.style.transform = 'none';
};
playerBox.onclick = () => showChestList(playerID, playerName, playerData.开箱数据);
// 玩家名称
let playerText = document.createElement('span');
playerText.style.flex = '1';
playerText.style.fontSize = '1.1em';
playerText.style.color = '#ffffff';
playerText.textContent = playerName;
// 删除按钮
let deleteButton = document.createElement('button');
deleteButton.textContent = '×';
deleteButton.style.backgroundColor = 'red';
deleteButton.style.color = 'white';
deleteButton.style.border = 'none';
deleteButton.style.borderRadius = '50%';
deleteButton.style.width = '24px';
deleteButton.style.height = '24px';
deleteButton.style.cursor = 'pointer';
deleteButton.onclick = (e) => {
e.stopPropagation(); // 防止触发父元素的点击事件
if (confirm(`是否删除 ${playerName} 的全部开箱数据?`)) {
deletePlayerChestData(playerID);
createVisualizationWindow(JSON.parse(localStorage.getItem('Edible_Tools')).Chest_Open_Data);
}
};
playerBox.appendChild(playerText);
playerBox.appendChild(deleteButton);
contentDiv.appendChild(playerBox);
}
windowDiv.appendChild(contentDiv);
// 关闭按钮
let closeButton = document.createElement('button');
closeButton.textContent = isCN ? '关闭' : 'Close';
closeButton.style.marginTop = '10px';
closeButton.style.padding = '10px';
closeButton.style.backgroundColor = '#4357af';
closeButton.style.color = 'white';
closeButton.style.border = 'none';
closeButton.style.borderRadius = '5px';
closeButton.style.cursor = 'pointer';
closeButton.onclick = () => document.body.removeChild(windowDiv);
windowDiv.appendChild(closeButton);
document.body.appendChild(windowDiv);
}
function deletePlayerChestData(playerID) {
let edibleToolsData = JSON.parse(localStorage.getItem('Edible_Tools'));
if (edibleToolsData && edibleToolsData.Chest_Open_Data) {
delete edibleToolsData.Chest_Open_Data[playerID];
localStorage.setItem('Edible_Tools', JSON.stringify(edibleToolsData));
}
}
function showChestList(playerID, playerName, chestData) {
let oldWindow = document.querySelector('.visualization-window');
if (oldWindow) oldWindow.remove();
let windowDiv = createWindowBase();
windowDiv.style.minHeight = '300px';
windowDiv.style.maxWidth = '400px';
// 标题
let title = document.createElement('h1');
title.innerText = isCN ? '开箱记录' : 'Chest Records';
title.style.color = '#98a7e9';
title.style.margin = '0';
title.style.fontSize = '1.5em';
title.style.textAlign = 'center';
windowDiv.appendChild(title);
// 内容区域
let contentDiv = document.createElement('div');
contentDiv.style.flex = '1';
contentDiv.style.overflowY = 'auto';
contentDiv.style.paddingRight = '8px';
contentDiv.style.display = 'flex';
contentDiv.style.flexDirection = 'column';
contentDiv.style.gap = '10px';
// 箱子列表
for (let chestName in chestData) {
let chest = chestData[chestName];
let chestBox = document.createElement('div');
chestBox.style.display = 'flex';
chestBox.style.alignItems = 'center';
chestBox.style.border = '1px solid #98a7e9';
chestBox.style.borderRadius = '8px';
chestBox.style.padding = '12px';
chestBox.style.cursor = 'pointer';
chestBox.style.backgroundColor = '#1e1e2f';
chestBox.style.transition = 'all 0.3s ease';
// 悬停效果
chestBox.onmouseenter = () => {
chestBox.style.backgroundColor = '#2c2e45';
chestBox.style.transform = 'translateX(5px)';
};
chestBox.onmouseleave = () => {
chestBox.style.backgroundColor = '#1e1e2f';
chestBox.style.transform = 'none';
};
chestBox.onclick = () => showChestDetails(playerID, playerName, chestName, chest);
// 图标
let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgIcon.setAttribute('width', '20');
svgIcon.setAttribute('height', '20');
svgIcon.style.marginRight = '12px';
svgIcon.style.flexShrink = '0';
let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
try {
let iconId = item_name_to_hrid[chestName].split('/').pop();
useElement.setAttribute('href', `${item_icon_url}#${iconId}`);
} catch (error) {
useElement.setAttribute('href', `${item_icon_url}#coin`);
}
svgIcon.appendChild(useElement);
// 文字
let chestText = document.createElement('span');
chestText.style.flex = '1';
chestText.style.fontSize = '0.95em';
chestText.innerHTML = `
<div style="color: #98a7e9;">${isCN && e2c[chestName] ? e2c[chestName] : chestName}</div>
<div style="color: #ffffff; font-size: 1.1em;">${chest['总计开箱数量']}</div>
`;
// 删除按钮
let deleteButton = document.createElement('button');
deleteButton.textContent = '×';
deleteButton.style.backgroundColor = 'red';
deleteButton.style.color = 'white';
deleteButton.style.border = 'none';
deleteButton.style.borderRadius = '50%';
deleteButton.style.width = '24px';
deleteButton.style.height = '24px';
deleteButton.style.cursor = 'pointer';
deleteButton.onclick = (e) => {
e.stopPropagation();
if (confirm(`是否删除 ${isCN && e2c[chestName] ? e2c[chestName] : chestName} 的开箱数据?`)) {
deleteChestData(playerID, chestName);
showChestList(playerID, playerName, JSON.parse(localStorage.getItem('Edible_Tools')).Chest_Open_Data[playerID].开箱数据);
}
};
chestBox.appendChild(svgIcon);
chestBox.appendChild(chestText);
chestBox.appendChild(deleteButton);
contentDiv.appendChild(chestBox);
}
windowDiv.appendChild(contentDiv);
// 底部按钮
let footerDiv = document.createElement('div');
footerDiv.style.display = 'flex';
footerDiv.style.gap = '10px';
footerDiv.style.marginTop = '10px';
const buttonStyle = {
flex: '1',
backgroundColor: '#4357af',
color: 'white',
border: 'none',
padding: '10px',
borderRadius: '6px',
cursor: 'pointer',
transition: 'background-color 0.3s',
fontSize: '0.95em'
};
// 返回按钮
let backButton = document.createElement('button');
Object.assign(backButton.style, buttonStyle);
backButton.innerText = isCN ? '返回' : 'Back';
backButton.onclick = () => {
windowDiv.remove();
createVisualizationWindow(JSON.parse(localStorage.getItem('Edible_Tools')).Chest_Open_Data);
};
// 关闭按钮
let closeButton = document.createElement('button');
Object.assign(closeButton.style, buttonStyle);
closeButton.innerText = isCN ? '关闭' : 'Close';
closeButton.onclick = () => windowDiv.remove();
footerDiv.appendChild(backButton);
footerDiv.appendChild(closeButton);
windowDiv.appendChild(footerDiv);
document.body.appendChild(windowDiv);
}
function deleteChestData(playerID, chestName) {
let edibleToolsData = JSON.parse(localStorage.getItem('Edible_Tools'));
if (edibleToolsData && edibleToolsData.Chest_Open_Data && edibleToolsData.Chest_Open_Data[playerID]) {
delete edibleToolsData.Chest_Open_Data[playerID].开箱数据[chestName];
localStorage.setItem('Edible_Tools', JSON.stringify(edibleToolsData));
}
}
function showChestDetails(playerID, playerName, chestName, chestData) {
let oldWindow = document.querySelector('.visualization-window');
if (oldWindow) oldWindow.remove();
let detailsWindow = createWindowBase();
detailsWindow.style.minWidth = '300px';
detailsWindow.style.maxWidth = '400px';
// 标题
let title = document.createElement('div');
title.style.display = 'flex';
title.style.alignItems = 'center';
title.style.justifyContent = 'center';
title.style.gap = '10px';
title.style.margin = '0 0 15px 0';
let titleSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
titleSvg.setAttribute('width', '28');
titleSvg.setAttribute('height', '28');
let iconId = item_name_to_hrid[chestName].split('/').pop();
titleSvg.innerHTML = `<use href="${item_icon_url}#${iconId}"/>`;
let titleText = document.createElement('span');
titleText.style.color = '#98a7e9';
titleText.style.fontSize = '1.4em';
titleText.textContent = isCN && e2c[chestName] ? e2c[chestName] : chestName;
title.appendChild(titleSvg);
title.appendChild(titleText);
detailsWindow.appendChild(title);
// 内容区域
let contentDiv = document.createElement('div');
contentDiv.style.flex = '1';
contentDiv.style.overflowY = 'auto';
contentDiv.style.display = 'flex';
contentDiv.style.flexDirection = 'column';
contentDiv.style.gap = '12px';
contentDiv.style.paddingRight = '8px';
// 统计卡片
let statsCard = document.createElement('div');
statsCard.style.backgroundColor = '#1e1e2f';
statsCard.style.borderRadius = '8px';
statsCard.style.padding = '15px';
statsCard.innerHTML = `
<div style="color: #98a7e9; margin-bottom: 10px;">📋 ${isCN ? "统计概览" : "Statistics Overview"}</div>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px;">
<div>${isCN ? "开箱总数" : "Total Open"}</div>
<div style="color: #ffffff; text-align: right;">${chestData['总计开箱数量']}</div>
<div>${isCN ? "Ask 总值" : "Total Ask"}</div>
<div style="color: #4CAF50; text-align: right;">${formatPrice(chestData['总计开箱Ask'])}</div>
<div>${isCN ? "Bid 总值" : "Total Bid"}</div>
<div style="color: orange; text-align: right;">${formatPrice(chestData['总计开箱Bid'])}</div>
<div>${isCN ? (chestData['累计偏差值'] < 0 ? "低于期望" : "高于期望") : (chestData['累计偏差值'] < 0 ? "Below Expectation" : "Above Expectation")}</div>
<div style="color: ${chestData['累计偏差值'] < 0 ? '#F44336' : '#4CAF50'}; text-align: right;">${formatPrice(Math.abs(chestData['累计偏差值'] || 0))}</div>
${chestData['总计最高利润'] !== undefined ? `
<div>${isCN ? "期望利润" : "Expected Profit"}</div>
<div style="color: ${
(chestData['总计最低利润'] > 0 && chestData['总计最高利润'] > 0) ? '#4CAF50' :
(chestData['总计最低利润'] < 0 && chestData['总计最高利润'] < 0) ? '#F44336' :
'#FFEB3B'
}; text-align: right;">
${formatPrice(chestData['总计最低利润'])}~${formatPrice(chestData['总计最高利润'])}
</div>
` : ''}
</div>
`;
contentDiv.appendChild(statsCard);
// 物品列表
let itemListHeader = document.createElement('div');
itemListHeader.style.color = '#98a7e9';
itemListHeader.innerText = isCN ? '🎁 获得物品' : "🎁 Get Item";
contentDiv.appendChild(itemListHeader);
const sortedItems = Object.entries(chestData['获得物品']).sort((a, b) => {
const getValidValue = (val) => val === -1 ? 0 : val;
const aAsk = getValidValue(a[1]['总计Ask价值']);
const aBid = getValidValue(a[1]['总计Bid价值']);
const bAsk = getValidValue(b[1]['总计Ask价值']);
const bBid = getValidValue(b[1]['总计Bid价值']);
return (bAsk + bBid) - (aAsk + aBid);
});
sortedItems.forEach(([itemName, item]) => {
let itemBox = document.createElement('div');
itemBox.style.display = 'flex';
itemBox.style.alignItems = 'center';
itemBox.style.backgroundColor = '#1e1e2f';
itemBox.style.border = '1px solid #98a7e9';
itemBox.style.borderRadius = '8px';
itemBox.style.padding = '12px';
itemBox.style.gap = '10px';
// 图标
let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgIcon.setAttribute('width', '24');
svgIcon.setAttribute('height', '24');
let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
try {
let iconId = item_name_to_hrid[itemName].split('/').pop();
useElement.setAttribute('href', `${item_icon_url}#${iconId}`);
} catch (error) {
useElement.setAttribute('href', `${item_icon_url}#coin`);
}
svgIcon.appendChild(useElement);
// 文字
let itemText = document.createElement('div');
itemText.style.flex = '1';
itemText.innerHTML = `
<div style="color: #ffffff;">${isCN && e2c[itemName] ? e2c[itemName] : itemName}</div>
<div style="color: #98a7e9; font-size: 0.9em;">${isCN ? "数量" : "Count"}: ${formatPrice(item['数量'])}</div>
`;
itemBox.appendChild(svgIcon);
itemBox.appendChild(itemText);
contentDiv.appendChild(itemBox);
});
detailsWindow.appendChild(contentDiv);
// 底部按钮
let footerDiv = document.createElement('div');
footerDiv.style.display = 'flex';
footerDiv.style.gap = '10px';
footerDiv.style.marginTop = '10px';
const buttonStyle = {
flex: '1',
backgroundColor: '#4357af',
color: 'white',
border: 'none',
padding: '10px',
borderRadius: '6px',
cursor: 'pointer',
transition: 'background-color 0.3s'
};
// 返回按钮
let backButton = document.createElement('button');
Object.assign(backButton.style, buttonStyle);
backButton.innerText = isCN ? '返回' : 'Back';
backButton.onclick = () => {
detailsWindow.remove();
showChestList(playerID, playerName, JSON.parse(localStorage.getItem('Edible_Tools')).Chest_Open_Data[playerID].开箱数据);
};
// 关闭按钮
let closeButton = document.createElement('button');
Object.assign(closeButton.style, buttonStyle);
closeButton.innerText = isCN ? '关闭' : 'Close';
closeButton.onclick = () => detailsWindow.remove();
footerDiv.appendChild(backButton);
footerDiv.appendChild(closeButton);
detailsWindow.appendChild(footerDiv);
document.body.appendChild(detailsWindow);
}
GM_registerMenuCommand('打印全部开箱记录', function() {
const edibleTools = JSON.parse(localStorage.getItem('Edible_Tools')) || {};
const openChestData = edibleTools.Chest_Open_Data || {};
createVisualizationWindow(openChestData);
});
GM_registerMenuCommand('打印掉落物列表', function() {
let dataHtml = '<div style="display: flex; flex-wrap: nowrap;">';
const minPrice = 10000;
for (let player in battlePlayerLoot) {
let totalPrice = 0;
dataHtml += `<div style="flex: 1 0 auto; min-width: 100px; margin: 10px; padding: 10px; border: 1px solid black;">`;
dataHtml += `<h3>${player}</h3>`;
let lootItems = battlePlayerLoot[player];
for (let item in lootItems) {
let bidPrice = getSpecialItemPrice(item,"bid") || 0;
totalPrice += bidPrice*lootItems[item].数量
if (bidPrice > minPrice) {
dataHtml += `<p>${item}: ${lootItems[item].数量}</p>`;
}
}
if (totalPrice > 0) {
dataHtml += `<p>总计价格: ${formatPrice(totalPrice)}</p>`;
}
dataHtml += '</div>';
}
dataHtml += '</div>';
let popup = document.createElement('div');
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.backgroundColor = 'white';
popup.style.border = '1px solid black';
popup.style.padding = '10px';
popup.style.zIndex = '10000';
popup.style.maxWidth = '75%';
popup.style.overflowX = 'auto';
popup.style.whiteSpace = 'nowrap';
popup.innerHTML = dataHtml;
let closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.display = 'block';
closeButton.style.margin = '20px auto 0 auto';
closeButton.onclick = function() {
document.body.removeChild(popup);
};
popup.appendChild(closeButton);
document.body.appendChild(popup);
});
function formatToChinesetime(timestamp) {
const date = new Date(timestamp);
const beijingOffset = 8 * 60;
date.setMinutes(date.getMinutes() + date.getTimezoneOffset() + beijingOffset);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}/${month}/${day} ${hours}:${minutes}`;
}
function openSettings() {
const tran_market_list = {
"/market_listing_status/filled": "已完成",
"/market_listing_status/active": "进行中",
"/market_listing_status/cancelled": "取消",
"/market_listing_status/expired": "超时",
};
const market_List_Data = JSON.parse(GM_getValue('market_list', '[]'));
const hrid2name = item_hrid_to_name;
// 格式化市场数据
market_List_Data.forEach(item => {
item.itemName = hrid2name[item.itemHrid] || item.itemHrid;
if (item.lastUpdated) {
item.format_lastUpdated = formatToChinesetime(item.lastUpdated);
}
});
const settingsContainer = document.createElement('div');
settingsContainer.style.position = 'fixed';
settingsContainer.style.top = '0';
settingsContainer.style.left = '0';
settingsContainer.style.width = '100%';
settingsContainer.style.height = '100%';
settingsContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
settingsContainer.style.zIndex = '9999';
settingsContainer.style.display = 'flex';
settingsContainer.style.flexDirection = 'column';
// 页面内容
const Edible_Tools_HTML = `
<div style="flex: 1; overflow-y: auto; background-color: #fff; padding: 20px;">
<header style="background-color: #4CAF50; color: white; padding: 10px 20px; text-align: center;">
<h1>银河奶牛数据库</h1>
</header>
<div style="display: flex; flex: 1;">
<div style="width: 200px; background-color: #f4f4f4; padding: 10px;">
<button id="showMarketDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #f44336; border: none;">市场数据</button>
<button id="showOpenChestDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #f44336; border: none;">开箱数据</button>
<button id="showEnhancementDataBtn" style="width: 100%; padding: 10px; margin: 5px 0; background-color: #f44336; border: none;">强化数据</button>
</div>
<div style="flex: 1; padding: 20px; overflow-y: auto; display: block;" id="showMarketDataPage">
<h2 style="text-align: center;">市场数据</h2>
<div style="text-align: center; margin-bottom: 20px;">
<button id="deleteOldDataBtn" style="padding: 10px 20px; margin: 0 10px; background-color: #f44336; color: white; border: none;">删除过时市场数据</button>
<button id="deleteSpecificStatusDataBtn" style="padding: 10px 20px; margin: 0 10px; background-color: #f44336; color: white; border: none;">仅保留已完成订单</button>
</div>
<table class="marketList-table" style="width: 100%; border-collapse: collapse;">
<thead>
<tr>
<th data-sort="id">订单ID</th>
<th data-sort="characterID">角色ID</th>
<th data-sort="status">状态</th>
<th data-sort="isSell">类型</th>
<th data-sort="itemName">物品</th>
<th data-sort="orderQuantity">数量</th>
<th data-sort="filledQuantity">已交易数量</th>
<th data-sort="price">单价</th>
<th data-sort="total">贸易额</th>
<th data-sort="format_lastUpdated">更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="marketDataTableBody">
<!-- 数据表会在这里插入 -->
</tbody>
</table>
</div>
<div style="flex: 1; padding: 20px; overflow-y: auto; display: none;" id="OpenChestDataPage">
<h2 style="text-align: center;">开箱数据(咕?)</h2>
</div>
<div style="flex: 1; padding: 20px; overflow-y: auto; display: none;" id="EnhancementDataPage">
<h2 style="text-align: center;">强化数据(咕咕~)</h2>
</div>
</div>
</div>
<button id="closeSettingsBtn" style="position: absolute; top: 10px; right: 10px; padding: 10px 20px; background-color: red; color: white; border: none;">关闭</button>
`;
settingsContainer.innerHTML = Edible_Tools_HTML;
document.body.appendChild(settingsContainer);
const marketDataPage = document.getElementById('showMarketDataPage');
const OpenChestDataPage = document.getElementById('OpenChestDataPage');
const EnhancementDataPage = document.getElementById('EnhancementDataPage');
let currentPage = 1; // 当前页码
let rowsPerPage = 10; // 每页显示的行数
function showMarketData() {
marketDataPage.style.display = 'block';
OpenChestDataPage.style.display = 'none';
EnhancementDataPage.style.display = 'none';
const tableBody = document.getElementById('marketDataTableBody');
const startIndex = (currentPage - 1) * rowsPerPage;
const endIndex = startIndex + rowsPerPage;
const paginatedData = market_List_Data.slice(startIndex, endIndex);
tableBody.innerHTML = paginatedData.map((row, index) => {
// 创建图标
let svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgIcon.setAttribute('width', '20');
svgIcon.setAttribute('height', '20');
svgIcon.style.marginRight = '10px';
svgIcon.style.verticalAlign = 'middle';
let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
try {
let iconId = row.itemHrid.split('/').pop();
useElement.setAttribute('href', `${item_icon_url}#${iconId}`);
} catch (error) {
console.error(`无法找到物品的图标ID:`, error);
useElement.setAttribute('href', `${item_icon_url}#coin`);
}
svgIcon.appendChild(useElement);
let translatedName = isCN && e2c[row.itemName] ? e2c[row.itemName] : row.itemName;
if (row.enhancementLevel > 0) {
translatedName = `${translatedName} +${row.enhancementLevel}`;
}
let itemNameWithIcon = `${svgIcon.outerHTML}${translatedName}`;
const globalIndex = startIndex + index; // 计算全局索引
return `
<tr data-index="${globalIndex}">
<td>${row.id}</td>
<td>${row.characterID}</td>
<td>${tran_market_list[row.status] || row.status}</td>
<td>${row.isSell ? '出售' : '收购'}</td>
<td>${itemNameWithIcon}</td>
<td>${(row.orderQuantity).toLocaleString()}</td>
<td>${(row.filledQuantity).toLocaleString()}</td>
<td>${(row.price).toLocaleString()}</td>
<td>${(row.price * row.filledQuantity).toLocaleString()}</td>
<td>${row.format_lastUpdated}</td>
<td><button class="delete-btn">删除</button></td>
</tr>
`;
}).join('');
updatePaginationControls();
attachDeleteListeners();
}
// 添加分页控件
const paginationControls = document.createElement('div');
paginationControls.style.textAlign = 'center';
paginationControls.style.marginTop = '20px';
paginationControls.innerHTML = `
<button id="prevPageBtn" style="padding: 5px 10px; margin: 0 5px;">上一页</button>
<span id="currentPageDisplay">第 ${currentPage} 页</span>
<button id="nextPageBtn" style="padding: 5px 10px; margin: 0 5px;">下一页</button>
<label style="margin-left: 10px;">
每页显示
<input id="rowsPerPageInput" type="number" value="${rowsPerPage}" min="1" style="width: 50px; text-align: center;">
行
</label>
`;
marketDataPage.appendChild(paginationControls);
// 更新分页控件状态
function updatePaginationControls() {
const totalPages = Math.ceil(market_List_Data.length / rowsPerPage);
document.getElementById('currentPageDisplay').textContent = `第 ${currentPage} 页 / 共 ${totalPages} 页`;
document.getElementById('prevPageBtn').disabled = currentPage === 1;
document.getElementById('nextPageBtn').disabled = currentPage === totalPages;
}
// 绑定分页控件事件
document.getElementById('prevPageBtn').addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
showMarketData();
}
});
document.getElementById('nextPageBtn').addEventListener('click', () => {
const totalPages = Math.ceil(market_List_Data.length / rowsPerPage);
if (currentPage < totalPages) {
currentPage++;
showMarketData();
}
});
document.getElementById('rowsPerPageInput').addEventListener('change', (event) => {
const newRowsPerPage = parseInt(event.target.value, 10);
if (newRowsPerPage > 0) {
rowsPerPage = newRowsPerPage;
currentPage = 1; // 重置到第一页
showMarketData();
}
});
function ShowOpenChestData() {
marketDataPage.style.display = 'none';
OpenChestDataPage.style.display = 'block';
EnhancementDataPage.style.display = 'none';
}
function ShowEnhancementData() {
marketDataPage.style.display = 'none';
OpenChestDataPage.style.display = 'none';
EnhancementDataPage.style.display = 'block';
}
showMarketData();
// 删除单行
function attachDeleteListeners() {
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', (event) => {
const row = event.target.closest('tr');
const index = parseInt(row.getAttribute('data-index'), 10);
market_List_Data.splice(index, 1);
GM_setValue('market_list', JSON.stringify(market_List_Data));
showMarketData();
});
});
}
attachDeleteListeners();// 初始绑定删除按钮事件
// 排序功能
let sortOrder = { field: null, direction: 1 };// 1 是升序,-1 是降序
function sortTable(column) {
const field = column.getAttribute('data-sort');
const direction = sortOrder.field === field && sortOrder.direction === 1 ? -1 : 1;// 切换排序方向
market_List_Data.sort((a, b) => {
if (field === 'total') {
return (a.price * a.filledQuantity - b.price * b.filledQuantity) * direction;
}
if (typeof a[field] === 'string') {
return (a[field].localeCompare(b[field])) * direction;
}
return (a[field] - b[field]) * direction;
});
// 更新排序状态
document.querySelectorAll('th').forEach(th => {
th.classList.remove('sort-asc', 'sort-desc');
});
column.classList.add(direction === 1 ? 'sort-asc' : 'sort-desc');
sortOrder = { field, direction };
showMarketData();
attachDeleteListeners();
}
// 给每个表头添加点击事件监听器
document.querySelectorAll('th').forEach(th => {
th.addEventListener('click', () => {
sortTable(th);
});
});
// 切换数据库页面
document.getElementById('showMarketDataBtn').addEventListener('click', showMarketData);
document.getElementById('showOpenChestDataBtn').addEventListener('click', ShowOpenChestData);
document.getElementById('showEnhancementDataBtn').addEventListener('click', ShowEnhancementData);
// 关闭按钮
document.getElementById('closeSettingsBtn').addEventListener('click', () => {
document.body.removeChild(settingsContainer);
});
// 删除过时市场数据
document.getElementById('deleteOldDataBtn').addEventListener('click', () => {
const userInput = prompt("请输入要删除之前的日期 (格式:YYYY-MM-DD)", "");
if (!userInput) return;
// 转换用户输入的日期为 Date 对象
const userDate = new Date(userInput);
if (isNaN(userDate)) {
alert("无效的日期格式,请使用 YYYY-MM-DD");
return;
}
let market_list = JSON.parse(GM_getValue('market_list', '[]'));
// 过滤出所有在用户选择日期之前的订单
const filteredMarketList = market_list.filter(order => {
const orderDate = new Date(order.lastUpdated);
return orderDate >= userDate;
});
// 更新并保存新的数据
GM_setValue('market_list', JSON.stringify(filteredMarketList));
alert("删除成功,已清理日期之前的数据。");
document.body.removeChild(settingsContainer);
});
document.getElementById('deleteSpecificStatusDataBtn').addEventListener('click', () => {
let market_list = JSON.parse(GM_getValue('market_list', '[]'));
const statusToDelete = ["/market_listing_status/active","进行中","/market_listing_status/cancelled","取消","/market_listing_status/expired","超时"];
const deleteCount = market_list.filter(order => statusToDelete.includes(order.status)).length;
if (deleteCount === 0) {
alert("没有需要删除的数据。");
return;
}
const isConfirmed = confirm(`即将删除 ${deleteCount} 条数据,是否继续?`);
if (!isConfirmed) {
return;
}
const filteredMarketList = market_list.filter(order => !statusToDelete.includes(order.status));
GM_setValue('market_list', JSON.stringify(filteredMarketList));
alert("删除成功");
document.body.removeChild(settingsContainer);
});
// 表格样式
const style = document.createElement('style');
style.innerHTML = `
.marketList-table {
width: 100%;
border-collapse: collapse;
}
.marketList-table, .marketList-table th, .marketList-table td {
border: 1px solid #f44336;
}
.marketList-table th, .marketList-table td {
padding: 10px;
text-align: center;
}
.marketList-table th {
background-color: #f2f2f2;
cursor: pointer;
}
.marketList-table th.sort-asc::after {
content: ' ▲';
}
.marketList-table th.sort-desc::after {
content: ' ▼';
}
`;
document.head.appendChild(style);
}
})();