MWITools

Tools for MilkyWayIdle. Shows total action time. Shows market prices. Shows action number quick inputs. Shows skill exp percentages. Shows total networth. Shows combat summary.

当前为 2024-05-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MWITools
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.0
  5. // @description Tools for MilkyWayIdle. Shows total action time. Shows market prices. Shows action number quick inputs. Shows skill exp percentages. Shows total networth. Shows combat summary.
  6. // @author bot7420
  7. // @match https://www.milkywayidle.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @connect raw.githubusercontent.com
  10. // ==/UserScript==
  11.  
  12. (() => {
  13. "use strict";
  14. const MARKET_JSON_LOCAL_BACKUP = `{"time":1715547603,"market":{"Amber":{"ask":6600,"bid":6200},"Amethyst":{"ask":94000,"bid":90000},"Apple":{"ask":6,"bid":5},"Apple Gummy":{"ask":16,"bid":10},"Apple Yogurt":{"ask":260,"bid":72},"Aqua Arrow":{"ask":22500,"bid":20500},"Aqua Essence":{"ask":28,"bid":18},"Arabica Coffee Bean":{"ask":125,"bid":115},"Arcane Bow":{"ask":250000,"bid":150000},"Arcane Crossbow":{"ask":230000,"bid":160000},"Arcane Fire Staff":{"ask":280000,"bid":-1},"Arcane Log":{"ask":260,"bid":205},"Arcane Lumber":{"ask":880,"bid":820},"Arcane Nature Staff":{"ask":270000,"bid":190000},"Arcane Water Staff":{"ask":280000,"bid":-1},"Artisan Tea":{"ask":680,"bid":540},"Attack Coffee":{"ask":500,"bid":360},"Azure Boots":{"ask":-1,"bid":12500},"Azure Brush":{"ask":49000,"bid":2100},"Azure Buckler":{"ask":11500,"bid":-1},"Azure Bulwark":{"ask":50000,"bid":-1},"Azure Cheese":{"ask":295,"bid":250},"Azure Chisel":{"ask":25500,"bid":-1},"Azure Enhancer":{"ask":49000,"bid":-1},"Azure Gauntlets":{"ask":35000,"bid":20500},"Azure Hammer":{"ask":21500,"bid":-1},"Azure Hatchet":{"ask":50000,"bid":-1},"Azure Helmet":{"ask":42000,"bid":14500},"Azure Mace":{"ask":-1,"bid":9600},"Azure Milk":{"ask":88,"bid":60},"Azure Needle":{"ask":21000,"bid":-1},"Azure Plate Body":{"ask":46000,"bid":16000},"Azure Plate Legs":{"ask":52000,"bid":16500},"Azure Pot":{"ask":30000,"bid":-1},"Azure Shears":{"ask":19500,"bid":-1},"Azure Spatula":{"ask":46000,"bid":2100},"Azure Spear":{"ask":-1,"bid":18500},"Azure Sword":{"ask":48000,"bid":-1},"Bamboo Boots":{"ask":41000,"bid":-1},"Bamboo Branch":{"ask":46,"bid":36},"Bamboo Fabric":{"ask":300,"bid":270},"Bamboo Gloves":{"ask":26500,"bid":-1},"Bamboo Hat":{"ask":15000,"bid":-1},"Bamboo Robe Bottoms":{"ask":20000,"bid":10000},"Bamboo Robe Top":{"ask":34000,"bid":-1},"Bear Essence":{"ask":70,"bid":56},"Beast Boots":{"ask":29000,"bid":-1},"Beast Bracers":{"ask":34000,"bid":-1},"Beast Chaps":{"ask":80000,"bid":30000},"Beast Hide":{"ask":21,"bid":20},"Beast Hood":{"ask":100000,"bid":30000},"Beast Leather":{"ask":300,"bid":265},"Beast Tunic":{"ask":185000,"bid":30000},"Berserk":{"ask":350000,"bid":310000},"Birch Bow":{"ask":28000,"bid":-1},"Birch Crossbow":{"ask":-1,"bid":-1},"Birch Fire Staff":{"ask":45000,"bid":800},"Birch Log":{"ask":62,"bid":56},"Birch Lumber":{"ask":265,"bid":100},"Birch Nature Staff":{"ask":22500,"bid":860},"Birch Water Staff":{"ask":28000,"bid":-1},"Black Bear Fluff":{"ask":50000,"bid":46000},"Black Bear Shoes":{"ask":440000,"bid":60000},"Black Tea Leaf":{"ask":26,"bid":21},"Blackberry":{"ask":32,"bid":24},"Blackberry Cake":{"ask":360,"bid":320},"Blackberry Donut":{"ask":330,"bid":90},"Blessed Tea":{"ask":840,"bid":700},"Blueberry":{"ask":27,"bid":20},"Blueberry Cake":{"ask":190,"bid":92},"Blueberry Donut":{"ask":175,"bid":82},"Brewing Tea":{"ask":49,"bid":22},"Burble Brush":{"ask":62000,"bid":10000},"Burble Buckler":{"ask":16000,"bid":-1},"Burble Bulwark":{"ask":29500,"bid":-1},"Burble Chisel":{"ask":45000,"bid":25500},"Burble Enhancer":{"ask":62000,"bid":5200},"Burble Gauntlets":{"ask":45000,"bid":20000},"Burble Hatchet":{"ask":64000,"bid":25000},"Burble Helmet":{"ask":54000,"bid":25000},"Burble Mace":{"ask":49000,"bid":25000},"Burble Needle":{"ask":38000,"bid":25000},"Burble Plate Body":{"ask":80000,"bid":13500},"Burble Pot":{"ask":66000,"bid":25000},"Burble Shears":{"ask":54000,"bid":-1},"Burble Spatula":{"ask":66000,"bid":25000},"Burble Sword":{"ask":-1,"bid":31000},"Burble Tea Leaf":{"ask":98,"bid":76},"Cedar Bow":{"ask":56000,"bid":-1},"Cedar Fire Staff":{"ask":30000,"bid":-1},"Cedar Log":{"ask":94,"bid":32},"Cedar Lumber":{"ask":330,"bid":300},"Cedar Water Staff":{"ask":45000,"bid":-1},"Centaur Boots":{"ask":1500000,"bid":-1},"Centaur Hoof":{"ask":150000,"bid":125000},"Cheese Boots":{"ask":-1,"bid":-1},"Cheese Brush":{"ask":6400,"bid":120},"Cheese Buckler":{"ask":960,"bid":-1},"Cheese Chisel":{"ask":3900,"bid":1850},"Cheese Enhancer":{"ask":2450,"bid":115},"Cheese Gauntlets":{"ask":2500,"bid":-1},"Cheese Hammer":{"ask":3600,"bid":160},"Cheese Helmet":{"ask":25000,"bid":-1},"Cheese Mace":{"ask":6800,"bid":-1},"Cheese Plate Body":{"ask":17500,"bid":150},"Cheese Plate Legs":{"ask":14000,"bid":-1},"Cheese Pot":{"ask":3900,"bid":360},"Cheese Spatula":{"ask":4300,"bid":720},"Cheese Spear":{"ask":18000,"bid":1000},"Cheese Sword":{"ask":6800,"bid":175},"Cleave":{"ask":130000,"bid":90000},"Cocoon":{"ask":150,"bid":115},"Coin":{"ask":-1,"bid":-1},"Cotton":{"ask":12,"bid":9},"Cotton Boots":{"ask":880,"bid":-1},"Cotton Fabric":{"ask":195,"bid":70},"Cotton Hat":{"ask":1250,"bid":-1},"Cotton Robe Bottoms":{"ask":7800,"bid":-1},"Cotton Robe Top":{"ask":-1,"bid":-1},"Crab Pincer":{"ask":15000,"bid":14000},"Crafting Tea":{"ask":680,"bid":44},"Crimson Boots":{"ask":38000,"bid":-1},"Crimson Buckler":{"ask":96000,"bid":-1},"Crimson Bulwark":{"ask":34000,"bid":-1},"Crimson Cheese":{"ask":390,"bid":300},"Crimson Enhancer":{"ask":50000,"bid":11000},"Crimson Gauntlets":{"ask":100000,"bid":30000},"Crimson Hammer":{"ask":50000,"bid":40000},"Crimson Helmet":{"ask":44000,"bid":38000},"Crimson Mace":{"ask":94000,"bid":50000},"Crimson Milk":{"ask":80,"bid":62},"Crimson Plate Body":{"ask":82000,"bid":47000},"Crimson Plate Legs":{"ask":80000,"bid":-1},"Crimson Pot":{"ask":58000,"bid":40000},"Crimson Spatula":{"ask":78000,"bid":40000},"Crimson Spear":{"ask":250000,"bid":70000},"Crimson Sword":{"ask":150000,"bid":70000},"Crushed Amber":{"ask":420,"bid":360},"Crushed Amethyst":{"ask":6200,"bid":5200},"Crushed Garnet":{"ask":1100,"bid":980},"Crushed Moonstone":{"ask":1800,"bid":1750},"Crushed Pearl":{"ask":1300,"bid":1000},"Cupcake":{"ask":195,"bid":105},"Donut":{"ask":150,"bid":39},"Dragon Fruit":{"ask":130,"bid":115},"Dragon Fruit Gummy":{"ask":410,"bid":370},"Earrings Of Armor":{"ask":4900000,"bid":1050000},"Earrings Of Gathering":{"ask":3800000,"bid":110000},"Earrings Of Regeneration":{"ask":6200000,"bid":3200000},"Earrings Of Resistance":{"ask":3500000,"bid":500000},"Efficiency Tea":{"ask":640,"bid":580},"Elemental Affinity":{"ask":620000,"bid":560000},"Emp Tea Leaf":{"ask":94,"bid":82},"Enhancing Tea":{"ask":275,"bid":190},"Excelsa Coffee Bean":{"ask":280,"bid":265},"Eyessence":{"ask":56,"bid":38},"Fieriosa Coffee Bean":{"ask":220,"bid":215},"Fireball":{"ask":11500,"bid":11000},"Flame Arrow":{"ask":19500,"bid":17500},"Flame Blast":{"ask":155000,"bid":145000},"Flaming Cloth":{"ask":41000,"bid":39000},"Flaming Robe Top":{"ask":470000,"bid":80000},"Flax":{"ask":90,"bid":35},"Foraging Tea":{"ask":350,"bid":45},"Garnet":{"ask":17000,"bid":15000},"Gathering Tea":{"ask":370,"bid":280},"Giant Pouch":{"ask":6000000,"bid":5200000},"Ginkgo Bow":{"ask":92000,"bid":-1},"Ginkgo Crossbow":{"ask":100000,"bid":-1},"Ginkgo Log":{"ask":62,"bid":50},"Ginkgo Lumber":{"ask":490,"bid":280},"Ginkgo Nature Staff":{"ask":92000,"bid":-1},"Gobo Boomstick":{"ask":20500,"bid":-1},"Gobo Boots":{"ask":40000,"bid":10500},"Gobo Bracers":{"ask":25000,"bid":-1},"Gobo Essence":{"ask":41,"bid":28},"Gobo Hide":{"ask":17,"bid":13},"Gobo Hood":{"ask":24000,"bid":-1},"Gobo Shooter":{"ask":20500,"bid":-1},"Gobo Slasher":{"ask":36000,"bid":20000},"Gobo Smasher":{"ask":20500,"bid":-1},"Gobo Tunic":{"ask":34000,"bid":30000},"Goggles":{"ask":50000,"bid":46000},"Golem Essence":{"ask":230,"bid":200},"Granite Bludgeon":{"ask":-1,"bid":39000000},"Green Tea Leaf":{"ask":25,"bid":13},"Grizzly Bear Fluff":{"ask":46000,"bid":42000},"Gummy":{"ask":380,"bid":42},"Heal":{"ask":210000,"bid":155000},"Holy Boots":{"ask":100000,"bid":-1},"Holy Buckler":{"ask":74000,"bid":-1},"Holy Bulwark":{"ask":205000,"bid":-1},"Holy Cheese":{"ask":840,"bid":780},"Holy Enhancer":{"ask":160000,"bid":125000},"Holy Gauntlets":{"ask":190000,"bid":-1},"Holy Hammer":{"ask":185000,"bid":130000},"Holy Helmet":{"ask":125000,"bid":-1},"Holy Mace":{"ask":270000,"bid":-1},"Holy Milk":{"ask":300,"bid":290},"Holy Plate Body":{"ask":290000,"bid":210000},"Holy Plate Legs":{"ask":-1,"bid":-1},"Holy Pot":{"ask":240000,"bid":105000},"Holy Spatula":{"ask":190000,"bid":155000},"Holy Spear":{"ask":200000,"bid":150000},"Holy Sword":{"ask":165000,"bid":160000},"Icy Cloth":{"ask":21500,"bid":17500},"Icy Robe Bottoms":{"ask":86000,"bid":60000},"Icy Robe Top":{"ask":120000,"bid":70000},"Jade":{"ask":35000,"bid":32000},"Jungle Essence":{"ask":54,"bid":27},"Large Artisan's Crate":{"ask":-1,"bid":-1},"Large Pouch":{"ask":960000,"bid":600000},"Large Treasure Chest":{"ask":-1,"bid":-1},"Liberica Coffee Bean":{"ask":230,"bid":210},"Linen Boots":{"ask":35000,"bid":680},"Linen Gloves":{"ask":13000,"bid":5000},"Linen Hat":{"ask":9800,"bid":-1},"Linen Robe Bottoms":{"ask":21000,"bid":1500},"Living Granite":{"ask":2400000,"bid":1950000},"Log":{"ask":25,"bid":18},"Lucky Coffee":{"ask":820,"bid":800},"Magic Coffee":{"ask":450,"bid":420},"Magnet":{"ask":125000,"bid":94000},"Magnifying Glass":{"ask":540000,"bid":310000},"Maim":{"ask":140000,"bid":72000},"Marsberry":{"ask":36,"bid":30},"Marsberry Donut":{"ask":490,"bid":460},"Medium Artisan's Crate":{"ask":-1,"bid":-1},"Medium Meteorite Cache":{"ask":-1,"bid":-1},"Medium Treasure Chest":{"ask":-1,"bid":-1},"Milk":{"ask":32,"bid":27},"Milking Tea":{"ask":300,"bid":125},"Minor Heal":{"ask":36000,"bid":6600},"Mooberry":{"ask":52,"bid":42},"Mooberry Cake":{"ask":360,"bid":290},"Mooberry Donut":{"ask":-1,"bid":215},"Moonstone":{"ask":43000,"bid":39000},"Necklace Of Efficiency":{"ask":-1,"bid":-1},"Necklace Of Wisdom":{"ask":7800000,"bid":5800000},"Orange Gummy":{"ask":25,"bid":21},"Orange Yogurt":{"ask":215,"bid":80},"Panda Gloves":{"ask":380000,"bid":-1},"Peach":{"ask":31,"bid":18},"Peach Gummy":{"ask":280,"bid":230},"Pearl":{"ask":16000,"bid":13000},"Pierce":{"ask":60000,"bid":48000},"Pincer Gloves":{"ask":48000,"bid":10000},"Plum":{"ask":68,"bid":56},"Plum Yogurt":{"ask":300,"bid":240},"Poke":{"ask":5200,"bid":5000},"Power Coffee":{"ask":520,"bid":420},"Precision":{"ask":39000,"bid":19000},"Purpleheart Bow":{"ask":78000,"bid":10000},"Purpleheart Crossbow":{"ask":-1,"bid":-1},"Purpleheart Fire Staff":{"ask":50000,"bid":-1},"Purpleheart Lumber":{"ask":400,"bid":300},"Purpleheart Nature Staff":{"ask":100000,"bid":-1},"Purpleheart Water Staff":{"ask":78000,"bid":-1},"Quick Shot":{"ask":4000,"bid":2800},"Radiant Fabric":{"ask":700,"bid":640},"Radiant Fiber":{"ask":125,"bid":100},"Radiant Gloves":{"ask":78000,"bid":-1},"Radiant Robe Bottoms":{"ask":180000,"bid":130000},"Radiant Robe Top":{"ask":185000,"bid":165000},"Rain Of Arrows":{"ask":260000,"bid":245000},"Rainbow Brush":{"ask":70000,"bid":30000},"Rainbow Buckler":{"ask":35000,"bid":-1},"Rainbow Bulwark":{"ask":66000,"bid":-1},"Rainbow Chisel":{"ask":120000,"bid":56000},"Rainbow Enhancer":{"ask":84000,"bid":54000},"Rainbow Gauntlets":{"ask":190000,"bid":25000},"Rainbow Hatchet":{"ask":120000,"bid":31000},"Rainbow Helmet":{"ask":84000,"bid":-1},"Rainbow Mace":{"ask":135000,"bid":-1},"Rainbow Needle":{"ask":115000,"bid":30000},"Rainbow Plate Body":{"ask":86000,"bid":62000},"Rainbow Plate Legs":{"ask":160000,"bid":-1},"Rainbow Shears":{"ask":115000,"bid":31000},"Rainbow Spatula":{"ask":120000,"bid":60000},"Rainbow Spear":{"ask":105000,"bid":74000},"Ranged Coffee":{"ask":560,"bid":470},"Ranger Necklace":{"ask":6000000,"bid":5000000},"Red Tea Leaf":{"ask":54,"bid":48},"Redwood Crossbow":{"ask":200000,"bid":-1},"Redwood Fire Staff":{"ask":86000,"bid":-1},"Redwood Log":{"ask":31,"bid":27},"Redwood Nature Staff":{"ask":-1,"bid":-1},"Redwood Water Staff":{"ask":88000,"bid":-1},"Reptile Boots":{"ask":7400,"bid":-1},"Reptile Chaps":{"ask":48000,"bid":1100},"Reptile Hide":{"ask":10,"bid":8},"Reptile Hood":{"ask":8200,"bid":-1},"Reptile Tunic":{"ask":18000,"bid":-1},"Ring Of Armor":{"ask":-1,"bid":1200000},"Ring Of Gathering":{"ask":5400000,"bid":1200000},"Ring Of Regeneration":{"ask":6400000,"bid":3600000},"Ring Of Resistance":{"ask":2650000,"bid":1600000},"Robusta Coffee Bean":{"ask":190,"bid":165},"Rough Bracers":{"ask":4000,"bid":-1},"Rough Chaps":{"ask":3000,"bid":-1},"Rough Hide":{"ask":68,"bid":43},"Rough Leather":{"ask":250,"bid":220},"Rough Tunic":{"ask":1000,"bid":-1},"Scratch":{"ask":4400,"bid":2900},"Silk Boots":{"ask":30000,"bid":-1},"Silk Fabric":{"ask":640,"bid":560},"Silk Gloves":{"ask":80000,"bid":-1},"Silk Robe Bottoms":{"ask":88000,"bid":58000},"Silk Robe Top":{"ask":245000,"bid":54000},"Smack":{"ask":8800,"bid":8000},"Small Meteorite Cache":{"ask":-1,"bid":-1},"Small Pouch":{"ask":17000,"bid":-1},"Snail Shell":{"ask":6600,"bid":3000},"Snail Shell Helmet":{"ask":13500,"bid":-1},"Snake Fang":{"ask":4400,"bid":2150},"Sorcerer Boots":{"ask":190000,"bid":100000},"Sorcerer Essence":{"ask":130,"bid":92},"Sorcerer's Sole":{"ask":66000,"bid":64000},"Spaceberry Cake":{"ask":960,"bid":920},"Spaceberry Donut":{"ask":700,"bid":660},"Spacia Coffee Bean":{"ask":490,"bid":470},"Stalactite Shard":{"ask":1800000,"bid":1450000},"Stalactite Spear":{"ask":-1,"bid":2050000},"Stamina Coffee":{"ask":350,"bid":290},"Star Fruit":{"ask":240,"bid":215},"Star Fruit Gummy":{"ask":640,"bid":600},"Star Fruit Yogurt":{"ask":860,"bid":820},"Strawberry Cake":{"ask":360,"bid":250},"Strawberry Donut":{"ask":390,"bid":205},"Stunning Blow":{"ask":480000,"bid":410000},"Super Attack Coffee":{"ask":1600,"bid":1100},"Super Brewing Tea":{"ask":500,"bid":100},"Super Cheesesmithing Tea":{"ask":2800,"bid":1850},"Super Crafting Tea":{"ask":3600,"bid":200},"Super Defense Coffee":{"ask":1700,"bid":1600},"Super Enhancing Tea":{"ask":2650,"bid":1200},"Super Foraging Tea":{"ask":4000,"bid":74},"Super Magic Coffee":{"ask":6800,"bid":6000},"Super Milking Tea":{"ask":1900,"bid":370},"Super Power Coffee":{"ask":2650,"bid":2300},"Super Stamina Coffee":{"ask":2000,"bid":1800},"Super Tailoring Tea":{"ask":6400,"bid":205},"Super Woodcutting Tea":{"ask":1550,"bid":640},"Sweep":{"ask":100000,"bid":80000},"Swiftness Coffee":{"ask":860,"bid":820},"Tailoring Tea":{"ask":520,"bid":72},"Tome Of The Elements":{"ask":320000,"bid":225000},"Toughness":{"ask":35000,"bid":34000},"Toxic Pollen":{"ask":175000,"bid":150000},"Turtle Shell Body":{"ask":12500,"bid":9000},"Turtle Shell Legs":{"ask":23000,"bid":6000},"Twilight Essence":{"ask":290,"bid":235},"Umbral Bracers":{"ask":88000,"bid":28000},"Umbral Chaps":{"ask":-1,"bid":-1},"Umbral Hide":{"ask":46,"bid":32},"Umbral Leather":{"ask":360,"bid":300},"Umbral Tunic":{"ask":185000,"bid":-1},"Vampire Fang":{"ask":1900000,"bid":1400000},"Vampirism":{"ask":40000,"bid":25000},"Verdant Boots":{"ask":-1,"bid":6600},"Verdant Brush":{"ask":8000,"bid":880},"Verdant Bulwark":{"ask":8200,"bid":-1},"Verdant Cheese":{"ask":215,"bid":185},"Verdant Chisel":{"ask":8000,"bid":3700},"Verdant Gauntlets":{"ask":9400,"bid":4900},"Verdant Hammer":{"ask":8000,"bid":600},"Verdant Hatchet":{"ask":34000,"bid":600},"Verdant Mace":{"ask":76000,"bid":9800},"Verdant Milk":{"ask":68,"bid":62},"Verdant Needle":{"ask":17500,"bid":560},"Verdant Plate Legs":{"ask":22000,"bid":12500},"Verdant Pot":{"ask":15500,"bid":1900},"Verdant Shears":{"ask":7800,"bid":720},"Verdant Spear":{"ask":22000,"bid":-1},"Verdant Sword":{"ask":22000,"bid":-1},"Vision Helmet":{"ask":48000,"bid":-1},"Water Strike":{"ask":16500,"bid":16000},"Werewolf Claw":{"ask":1100000,"bid":1000000},"Werewolf Slasher":{"ask":34000000,"bid":17000000},"Wisdom Coffee":{"ask":820,"bid":760},"Wisdom Tea":{"ask":640,"bid":540},"Wizard Necklace":{"ask":10000000,"bid":6000000},"Wooden Bow":{"ask":9600,"bid":-1},"Wooden Crossbow":{"ask":9600,"bid":-1},"Wooden Fire Staff":{"ask":10000,"bid":-1},"Wooden Water Staff":{"ask":5000,"bid":-1},"Yogurt":{"ask":275,"bid":60},"Burble Boots":{"ask":56000,"bid":20000},"Burble Cheese":{"ask":275,"bid":240},"Burble Hammer":{"ask":28500,"bid":25000},"Burble Milk":{"ask":98,"bid":78},"Cedar Nature Staff":{"ask":43000,"bid":2900},"Cheese":{"ask":145,"bid":96},"Cheese Bulwark":{"ask":4000,"bid":-1},"Cheese Hatchet":{"ask":9400,"bid":180},"Cheese Needle":{"ask":13500,"bid":130},"Cheese Shears":{"ask":14500,"bid":150},"Cheesesmithing Tea":{"ask":460,"bid":66},"Cooking Tea":{"ask":190,"bid":52},"Cotton Gloves":{"ask":1400,"bid":-1},"Cowbell":{"ask":-1,"bid":-1},"Crimson Brush":{"ask":76000,"bid":12000},"Crimson Chisel":{"ask":66000,"bid":41000},"Crimson Hatchet":{"ask":72000,"bid":43000},"Crimson Shears":{"ask":44000,"bid":-1},"Critical Coffee":{"ask":1700,"bid":1600},"Crushed Jade":{"ask":2600,"bid":1900},"Defense Coffee":{"ask":410,"bid":320},"Dragon Fruit Yogurt":{"ask":500,"bid":460},"Flaming Robe Bottoms":{"ask":370000,"bid":64000},"Frenzy":{"ask":90000,"bid":78000},"Gobo Leather":{"ask":310,"bid":205},"Holy Chisel":{"ask":195000,"bid":150000},"Holy Hatchet":{"ask":165000,"bid":94000},"Holy Needle":{"ask":185000,"bid":92000},"Holy Shears":{"ask":185000,"bid":150000},"Ice Spear":{"ask":86000,"bid":74000},"Intelligence Coffee":{"ask":360,"bid":275},"Linen Fabric":{"ask":300,"bid":215},"Linen Robe Top":{"ask":11000,"bid":-1},"Lumber":{"ask":190,"bid":105},"Mirror Of Protection":{"ask":5000000,"bid":4800000},"Moolong Tea Leaf":{"ask":64,"bid":50},"Orange":{"ask":19,"bid":6},"Panda Fluff":{"ask":48000,"bid":40000},"Peach Yogurt":{"ask":410,"bid":350},"Plum Gummy":{"ask":125,"bid":86},"Processing Tea":{"ask":820,"bid":660},"Purpleheart Log":{"ask":76,"bid":56},"Radiant Boots":{"ask":52000,"bid":-1},"Radiant Hat":{"ask":135000,"bid":105000},"Rainbow Boots":{"ask":165000,"bid":-1},"Rainbow Cheese":{"ask":400,"bid":370},"Rainbow Hammer":{"ask":105000,"bid":54000},"Rainbow Milk":{"ask":110,"bid":100},"Rainbow Pot":{"ask":86000,"bid":30000},"Rainbow Sword":{"ask":90000,"bid":70000},"Redwood Bow":{"ask":100000,"bid":-1},"Redwood Lumber":{"ask":190,"bid":130},"Reptile Bracers":{"ask":27500,"bid":-1},"Reptile Leather":{"ask":130,"bid":96},"Ring Of Rare Find":{"ask":4400000,"bid":3400000},"Rough Boots":{"ask":2350,"bid":-1},"Rough Hood":{"ask":72000,"bid":-1},"Shard Of Protection":{"ask":30000,"bid":29000},"Silk Hat":{"ask":92000,"bid":-1},"Small Artisan's Crate":{"ask":-1,"bid":-1},"Small Treasure Chest":{"ask":-1,"bid":-1},"Snake Fang Dirk":{"ask":4100,"bid":2500},"Spaceberry":{"ask":125,"bid":110},"Spike Shell":{"ask":180000,"bid":115000},"Star Fragment":{"ask":6400,"bid":6200},"Strawberry":{"ask":47,"bid":40},"Super Cooking Tea":{"ask":940,"bid":86},"Super Intelligence Coffee":{"ask":1950,"bid":1850},"Super Ranged Coffee":{"ask":3400,"bid":3200},"Swamp Essence":{"ask":21,"bid":20},"Tome Of Healing":{"ask":80000,"bid":40000},"Turtle Shell":{"ask":8000,"bid":2700},"Umbral Boots":{"ask":160000,"bid":-1},"Umbral Hood":{"ask":100000,"bid":-1},"Vampire Fang Dirk":{"ask":-1,"bid":22000000},"Verdant Buckler":{"ask":7400,"bid":-1},"Verdant Enhancer":{"ask":35000,"bid":560},"Verdant Helmet":{"ask":22500,"bid":3000},"Verdant Spatula":{"ask":16000,"bid":5800},"Vision Shield":{"ask":390000,"bid":100000},"Wheat":{"ask":20,"bid":18},"Woodcutting Tea":{"ask":400,"bid":190},"Wooden Nature Staff":{"ask":3900,"bid":150},"Cedar Crossbow":{"ask":39000,"bid":2600},"Earrings Of Rare Find":{"ask":4900000,"bid":3300000},"Egg":{"ask":21,"bid":18},"Entangle":{"ask":4400,"bid":2000},"Fighter Necklace":{"ask":9600000,"bid":-1},"Gator Vest":{"ask":6200,"bid":6000},"Ginkgo Fire Staff":{"ask":70000,"bid":-1},"Gobo Chaps":{"ask":27000,"bid":11000},"Gobo Stabber":{"ask":21000,"bid":-1},"Gourmet Tea":{"ask":380,"bid":280},"Grizzly Bear Shoes":{"ask":920000,"bid":68000},"Holy Brush":{"ask":185000,"bid":130000},"Large Meteorite Cache":{"ask":-1,"bid":-1},"Magnetic Gloves":{"ask":760000,"bid":460000},"Marsberry Cake":{"ask":540,"bid":500},"Medium Pouch":{"ask":165000,"bid":52000},"Polar Bear Fluff":{"ask":82000,"bid":80000},"Verdant Plate Body":{"ask":29500,"bid":12000},"Ginkgo Water Staff":{"ask":76000,"bid":-1},"Polar Bear Shoes":{"ask":-1,"bid":64000},"Sugar":{"ask":7,"bid":6},"Crimson Needle":{"ask":80000,"bid":-1},"Burble Plate Legs":{"ask":72000,"bid":40000},"Burble Spear":{"ask":-1,"bid":40000},"Arcane Shield":{"ask":165000,"bid":-1},"Birch Shield":{"ask":13000,"bid":-1},"Cedar Shield":{"ask":25000,"bid":-1},"Ginkgo Shield":{"ask":58000,"bid":-1},"Purpleheart Shield":{"ask":39000,"bid":-1},"Redwood Shield":{"ask":68000,"bid":22000},"Sighted Bracers":{"ask":760000,"bid":250000},"Spiked Bulwark":{"ask":52000000,"bid":410000},"Wooden Shield":{"ask":1950,"bid":96},"Advanced Task Ring":{"ask":-1,"bid":-1},"Basic Task Ring":{"ask":-1,"bid":-1},"Expert Task Ring":{"ask":-1,"bid":-1},"Purple's Gift":{"ask":-1,"bid":-1},"Task Crystal":{"ask":-1,"bid":-1},"Task Token":{"ask":-1,"bid":-1},"Abyssal Essence":{"ask":240,"bid":215},"Channeling Coffee":{"ask":700,"bid":660},"Chrono Gloves":{"ask":6800000,"bid":400000},"Chrono Sphere":{"ask":700000,"bid":560000},"Collector's Boots":{"ask":2500000,"bid":-1},"Colossus Core":{"ask":1050000,"bid":980000},"Colossus Plate Body":{"ask":13000000,"bid":8800000},"Colossus Plate Legs":{"ask":9000000,"bid":7400000},"Demonic Core":{"ask":1450000,"bid":1350000},"Demonic Plate Body":{"ask":16000000,"bid":8000000},"Demonic Plate Legs":{"ask":12500000,"bid":6200000},"Elusiveness":{"ask":26000,"bid":17500},"Enchanted Gloves":{"ask":6800000,"bid":5200000},"Eye Of The Watcher":{"ask":640000,"bid":600000},"Eye Watch":{"ask":7400000,"bid":3200000},"Firestorm":{"ask":640000,"bid":620000},"Fluffy Red Hat":{"ask":4200000,"bid":3200000},"Frost Sphere":{"ask":660000,"bid":560000},"Frost Staff":{"ask":15000000,"bid":11000000},"Frost Surge":{"ask":760000,"bid":600000},"Gobo Defender":{"ask":250000,"bid":235000},"Gobo Rag":{"ask":160000,"bid":135000},"Infernal Battlestaff":{"ask":35000000,"bid":24500000},"Infernal Ember":{"ask":1800000,"bid":1700000},"Luna Robe Bottoms":{"ask":2650000,"bid":-1},"Luna Robe Top":{"ask":1900000,"bid":110000},"Luna Wing":{"ask":185000,"bid":170000},"Marine Chaps":{"ask":1650000,"bid":-1},"Marine Scale":{"ask":220000,"bid":205000},"Marine Tunic":{"ask":2850000,"bid":1200000},"Nature's Veil":{"ask":800000,"bid":480000},"Puncture":{"ask":250000,"bid":185000},"Red Chef's Hat":{"ask":4000000,"bid":3300000},"Red Panda Fluff":{"ask":390000,"bid":370000},"Revenant Anima":{"ask":1400000,"bid":1300000},"Revenant Chaps":{"ask":10000000,"bid":320000},"Revenant Tunic":{"ask":12500000,"bid":9000000},"Shoebill Feather":{"ask":28000,"bid":22500},"Shoebill Shoes":{"ask":350000,"bid":-1},"Silencing Shot":{"ask":260000,"bid":240000},"Soul Fragment":{"ask":1150000,"bid":980000},"Soul Hunter Crossbow":{"ask":-1,"bid":10000000},"Steady Shot":{"ask":640000,"bid":580000},"Treant Bark":{"ask":21500,"bid":17500},"Treant Shield":{"ask":32000,"bid":-1},"Vampiric Bow":{"ask":41000000,"bid":410000},"Watchful Relic":{"ask":7400000,"bid":-1},"Bag Of 10 Cowbells":{"ask":290000,"bid":270000},"Aqua Aura":{"ask":3600000,"bid":2800000},"Critical Aura":{"ask":11000000,"bid":7600000},"Fierce Aura":{"ask":17000000,"bid":12500000},"Flame Aura":{"ask":5000000,"bid":4200000},"Insanity":{"ask":10000000,"bid":7000000},"Invincible":{"ask":66000000,"bid":37000000},"Provoke":{"ask":180000,"bid":160000},"Quick Aid":{"ask":880000,"bid":580000},"Rejuvenate":{"ask":880000,"bid":760000},"Revive":{"ask":1200000,"bid":700000},"Speed Aura":{"ask":8400000,"bid":6000000},"Sylvan Aura":{"ask":4500000,"bid":3700000},"Taunt":{"ask":92000,"bid":78000}}}`;
  15. let isUsingLocalMarketJson = false;
  16.  
  17. let initData_characterSkills = null;
  18. let initData_characterItems = null;
  19. let initData_characterHouseRoomMap = null;
  20. let initData_actionTypeDrinkSlotsMap = null;
  21. let initData_actionDetailMap = null;
  22. let initData_levelExperienceTable = null;
  23. let initData_itemDetailMap = null;
  24.  
  25. let currentActionsHridList = [];
  26.  
  27. hookWS();
  28.  
  29. fetchMarketJSON(true);
  30.  
  31. function hookWS() {
  32. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  33. const oriGet = dataProperty.get;
  34.  
  35. dataProperty.get = hookedGet;
  36. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  37.  
  38. function hookedGet() {
  39. const socket = this.currentTarget;
  40. if (!(socket instanceof WebSocket)) {
  41. return oriGet.call(this);
  42. }
  43. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1) {
  44. return oriGet.call(this);
  45. }
  46.  
  47. const message = oriGet.call(this);
  48. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  49.  
  50. return handleMessage(message);
  51. }
  52. }
  53.  
  54. function handleMessage(message) {
  55. let obj = JSON.parse(message);
  56. if (obj && obj.type === "init_character_data") {
  57. initData_characterSkills = obj.characterSkills;
  58. initData_characterItems = obj.characterItems;
  59. initData_characterHouseRoomMap = obj.characterHouseRoomMap;
  60. initData_actionTypeDrinkSlotsMap = obj.actionTypeDrinkSlotsMap;
  61. currentActionsHridList = [...obj.characterActions];
  62. showTotalActionTime();
  63. waitForActionPanelParent();
  64. calculateNetworth();
  65. } else if (obj && obj.type === "init_client_data") {
  66. initData_actionDetailMap = obj.actionDetailMap;
  67. initData_levelExperienceTable = obj.levelExperienceTable;
  68. initData_itemDetailMap = obj.itemDetailMap;
  69. } else if (obj && obj.type === "actions_updated") {
  70. for (const action of obj.endCharacterActions) {
  71. if (action.isDone === false) {
  72. let o = {};
  73. o.id = action.id;
  74. o.actionHrid = action.actionHrid;
  75. currentActionsHridList.push(o);
  76. } else {
  77. currentActionsHridList = currentActionsHridList.filter((o) => {
  78. return o.id !== action.id;
  79. });
  80. }
  81. }
  82. } else if (obj && obj.type === "battle_unit_fetched") {
  83. handleBattleSummary(obj);
  84. }
  85. return message;
  86. }
  87.  
  88. /* 计算Networth */
  89. async function calculateNetworth() {
  90. const marketAPIJson = await fetchMarketJSON();
  91. if (!marketAPIJson) {
  92. console.error("calculateNetworth marketAPIJson is null");
  93. return;
  94. }
  95.  
  96. let networthAsk = 0;
  97. let networthBid = 0;
  98. for (const item of initData_characterItems) {
  99. const itemName = initData_itemDetailMap[item.itemHrid].name;
  100. const marketPrices = marketAPIJson.market[itemName];
  101. if (marketPrices) {
  102. networthAsk += item.count * (marketPrices.ask > 0 ? marketPrices.ask : 0);
  103. networthBid += item.count * (marketPrices.bid > 0 ? marketPrices.bid : 0);
  104. }
  105. }
  106.  
  107. const waitForHeader = () => {
  108. const targetNode = document.querySelector("div.Header_totalLevel__8LY3Q");
  109. if (targetNode) {
  110. targetNode.insertAdjacentHTML(
  111. "afterend",
  112. `<div>Networth: ${numberFormatter(networthAsk)} / ${numberFormatter(networthBid)}${isUsingLocalMarketJson ? `<div style="color: red">需要科学网络更新市场数据</div>` : ""}</div>`
  113. );
  114. } else {
  115. setTimeout(waitForHeader, 200);
  116. }
  117. };
  118. waitForHeader();
  119. }
  120.  
  121. /* 显示当前动作总时间 */
  122. const showTotalActionTime = () => {
  123. const targetNode = document.querySelector("div.Header_actionName__31-L2");
  124. if (targetNode) {
  125. console.log("start observe action progress bar");
  126. calculateTotalTime(targetNode);
  127. new MutationObserver((mutationsList) =>
  128. mutationsList.forEach((mutation) => {
  129. calculateTotalTime();
  130. })
  131. ).observe(targetNode, { characterData: true, subtree: true, childList: true });
  132. } else {
  133. setTimeout(showTotalActionTime, 200);
  134. }
  135. };
  136.  
  137. function calculateTotalTime() {
  138. const targetNode = document.querySelector("div.Header_actionName__31-L2 > div.Header_actionName__31-L2");
  139. const textNode = [...targetNode.childNodes]
  140. .filter((child) => child.nodeType === Node.TEXT_NODE)
  141. .filter((child) => child.textContent.trim())
  142. .map((textNode) => textNode)[0];
  143. if (textNode.textContent.includes("[")) {
  144. return;
  145. }
  146. let totalTimeStr = "Error";
  147. if (targetNode.childNodes.length === 1) {
  148. totalTimeStr = " [" + timeReadable(0) + "]";
  149. } else if (targetNode.childNodes.length === 2) {
  150. const content = targetNode.innerText;
  151. const match = content.match(/\((\d+)\)/);
  152. if (match) {
  153. const numOfTimes = +match[1];
  154. const timePerActionSec = +document.querySelector(".ProgressBar_text__102Yn").textContent.match(/[\d\.]+/)[0];
  155. const actionHrid = currentActionsHridList[0].actionHrid;
  156. const effBuff = 1 + getTotalEffiPercentage(actionHrid) / 100;
  157. const actualNumberOfTimes = Math.round(numOfTimes / effBuff);
  158. totalTimeStr = " [" + timeReadable(actualNumberOfTimes * timePerActionSec) + "]";
  159. } else {
  160. totalTimeStr = " [∞]";
  161. }
  162. }
  163. textNode.textContent += totalTimeStr;
  164. }
  165.  
  166. function timeReadable(sec) {
  167. if (sec >= 86400) {
  168. return Number(sec / 86400).toFixed(1) + " 天";
  169. }
  170. const d = new Date(Math.round(sec * 1000));
  171. function pad(i) {
  172. return ("0" + i).slice(-2);
  173. }
  174. let str = d.getUTCHours() + "h " + pad(d.getUTCMinutes()) + "m " + pad(d.getUTCSeconds()) + "s";
  175. return str;
  176. }
  177.  
  178. /* 物品 ToolTips */
  179. const tooltipObserver = new MutationObserver(async function (mutations) {
  180. for (const mutation of mutations) {
  181. for (const added of mutation.addedNodes) {
  182. if (added.classList.contains("MuiTooltip-popper")) {
  183. if (added.querySelector("div.ItemTooltipText_name__2JAHA")) {
  184. await handleTooltipItem(added);
  185. }
  186. }
  187. }
  188. }
  189. });
  190. tooltipObserver.observe(document.body, { attributes: false, childList: true, characterData: false });
  191.  
  192. const actionHridToToolsSpeedBuffNamesMap = {
  193. "/action_types/brewing": "brewingSpeed",
  194. "/action_types/cheesesmithing": "cheesesmithingSpeed",
  195. "/action_types/cooking": "cookingSpeed",
  196. "/action_types/crafting": "craftingSpeed",
  197. "/action_types/foraging": "foragingSpeed",
  198. "/action_types/milking": "milkingSpeed",
  199. "/action_types/tailoring": "tailoringSpeed",
  200. "/action_types/woodcutting": "woodcuttingSpeed",
  201. };
  202.  
  203. const actionHridToHouseNamesMap = {
  204. "/action_types/brewing": "/house_rooms/brewery",
  205. "/action_types/cheesesmithing": "/house_rooms/forge",
  206. "/action_types/cooking": "/house_rooms/kitchen",
  207. "/action_types/crafting": "/house_rooms/workshop",
  208. "/action_types/foraging": "/house_rooms/garden",
  209. "/action_types/milking": "/house_rooms/dairy_barn",
  210. "/action_types/tailoring": "/house_rooms/sewing_parlor",
  211. "/action_types/woodcutting": "/house_rooms/log_shed",
  212. };
  213.  
  214. const itemEnhanceLevelToBuffBonusMap = {
  215. 0: 0,
  216. 1: 2,
  217. 2: 4.2,
  218. 3: 6.6,
  219. 4: 9.2,
  220. 5: 12.0,
  221. 6: 15.0,
  222. 7: 18.2,
  223. 8: 21.6,
  224. 9: 25.2,
  225. 10: 29.0,
  226. 11: 33.0,
  227. 12: 37.2,
  228. 13: 41.6,
  229. 14: 46.2,
  230. 15: 51.0,
  231. 16: 56.0,
  232. 17: 61.2,
  233. 18: 66.6,
  234. 19: 72.2,
  235. 20: 78.0,
  236. };
  237.  
  238. function getToolsSpeedBuffByActionHrid(actionHrid) {
  239. let buff = 0;
  240. for (const item of initData_characterItems) {
  241. if (item.itemLocationHrid.includes("_tool")) {
  242. const buffName = actionHridToToolsSpeedBuffNamesMap[initData_actionDetailMap[actionHrid].type];
  243. const enhanceBonus = 1 + itemEnhanceLevelToBuffBonusMap[item.enhancementLevel] / 100;
  244. buff += initData_itemDetailMap[item.itemHrid].equipmentDetail.noncombatStats[buffName] * enhanceBonus;
  245. }
  246. }
  247. return Number(buff * 100).toFixed(1);
  248. }
  249.  
  250. function getItemEffiBuffByActionHrid(actionHrid) {
  251. let buff = 0;
  252. const propertyName = initData_actionDetailMap[actionHrid].type.replace("/action_types/", "") + "Efficiency";
  253. for (const item of initData_characterItems) {
  254. const itemDetail = initData_itemDetailMap[item.itemHrid];
  255. const stat = itemDetail?.equipmentDetail?.noncombatStats[propertyName];
  256. if (stat && stat > 0) {
  257. let enhanceBonus = 1;
  258. if (item.itemLocationHrid.includes("earrings") || item.itemLocationHrid.includes("ring") || item.itemLocationHrid.includes("neck")) {
  259. enhanceBonus = 1 + (itemEnhanceLevelToBuffBonusMap[item.enhancementLevel] * 5) / 100;
  260. } else {
  261. enhanceBonus = 1 + itemEnhanceLevelToBuffBonusMap[item.enhancementLevel] / 100;
  262. }
  263. buff += stat * enhanceBonus;
  264. }
  265. }
  266. return Number(buff * 100).toFixed(1);
  267. }
  268.  
  269. function getHousesEffBuffByActionHrid(actionHrid) {
  270. const houseName = actionHridToHouseNamesMap[initData_actionDetailMap[actionHrid].type];
  271. if (!houseName) {
  272. return 0;
  273. }
  274. const house = initData_characterHouseRoomMap[houseName];
  275. if (!house) {
  276. return 0;
  277. }
  278. return house.level * 1.5;
  279. }
  280.  
  281. function getTeaBuffsByActionHrid(actionHrid) {
  282. // YES Gathering (+15% quantity) — milking, foraging, woodcutting
  283. // TODO Processing (+15% chance to convert product into processed material) — milking, foraging, woodcutting
  284. // YES Gourmet (+12% to produce free product) — cooking, brewing
  285. // YES Artisan (-10% less resources used, but treat as -5 levels) — cheesesmithing, crafting, tailoring, cooking, brewing
  286. // NO Wisdom (+12% XP) — all
  287. // YES Efficiency (+10% chance to repeat action) — all (except enhancing)
  288. // YES S.Skill (treat as +3 or +6 levels, different names) — all
  289. let teaBuffs = {
  290. efficiency: 0,
  291. quantity: 0,
  292. upgradedProduct: 0,
  293. lessResource: 0,
  294. };
  295.  
  296. const teaList = initData_actionTypeDrinkSlotsMap[initData_actionDetailMap[actionHrid].type];
  297. for (const tea of teaList) {
  298. if (!tea || !tea.itemHrid) {
  299. continue;
  300. }
  301. if (tea.itemHrid === "/items/efficiency_tea") {
  302. teaBuffs.efficiency += 10;
  303. continue;
  304. }
  305. const teaBuffDetail = initData_itemDetailMap[tea.itemHrid]?.consumableDetail?.buffs[0];
  306. if (teaBuffDetail && teaBuffDetail.typeHrid.includes("_level")) {
  307. teaBuffs.efficiency += teaBuffDetail.flatBoost;
  308. continue;
  309. }
  310. if (tea.itemHrid === "/items/artisan_tea") {
  311. teaBuffs.lessResource += 10;
  312. continue;
  313. }
  314. if (tea.itemHrid === "/items/gathering_tea") {
  315. teaBuffs.quantity += 15;
  316. continue;
  317. }
  318. if (tea.itemHrid === "/items/gourmet_tea") {
  319. teaBuffs.quantity += 12;
  320. continue;
  321. }
  322. if (tea.itemHrid === "/items/processing_tea") {
  323. teaBuffs.upgradedProduct += 15;
  324. continue;
  325. }
  326. }
  327. return teaBuffs;
  328. }
  329.  
  330. async function handleTooltipItem(tooltip) {
  331. const itemNameElem = tooltip.querySelector("div.ItemTooltipText_name__2JAHA");
  332. if (itemNameElem.querySelectorAll("span").length > 1) {
  333. return; // 不显示带有强化等级的物品
  334. }
  335. const itemName = itemNameElem.textContent;
  336. const amountSpan = tooltip.querySelectorAll("span")[1];
  337. let amount = 0;
  338. let insertAfterElem = null;
  339. if (amountSpan) {
  340. amount = +amountSpan.textContent.split(": ")[1].replaceAll(",", "");
  341. insertAfterElem = amountSpan.parentNode.nextSibling;
  342. } else {
  343. insertAfterElem = tooltip.querySelectorAll("span")[0].parentNode.nextSibling;
  344. }
  345.  
  346. const jsonObj = await fetchMarketJSON();
  347. if (!jsonObj || !jsonObj.market) {
  348. insertAfterElem.insertAdjacentHTML(
  349. "afterend",
  350. `
  351. <div style="color: DarkGreen;"">市场API错误</div>
  352. `
  353. );
  354. return;
  355. }
  356.  
  357. if (!jsonObj.market[itemName]) {
  358. console.error("itemName not found in market API json: " + itemName);
  359. }
  360.  
  361. let appendHTMLStr = "";
  362.  
  363. // 市场价格
  364. const ask = jsonObj?.market[itemName]?.ask;
  365. const bid = jsonObj?.market[itemName]?.bid;
  366. appendHTMLStr += `
  367. <div style="color: DarkGreen;"">日均价: ${numberFormatter(ask)} / ${numberFormatter(bid)} (${ask && ask > 0 ? numberFormatter(ask * amount) : ""} / ${
  368. bid && bid > 0 ? numberFormatter(bid * amount) : ""
  369. })</div>
  370. `;
  371.  
  372. if (
  373. getActionHridFromItemName(itemName) &&
  374. initData_actionDetailMap[getActionHridFromItemName(itemName)].inputItems &&
  375. initData_actionDetailMap[getActionHridFromItemName(itemName)].inputItems.length > 0 &&
  376. initData_actionDetailMap &&
  377. initData_itemDetailMap
  378. ) {
  379. // 制造类技能
  380. const actionHrid = getActionHridFromItemName(itemName);
  381. const inputItems = JSON.parse(JSON.stringify(initData_actionDetailMap[actionHrid].inputItems));
  382. const upgradedFromItemHrid = initData_actionDetailMap[actionHrid]?.upgradeItemHrid;
  383. if (upgradedFromItemHrid) {
  384. inputItems.push({ itemHrid: upgradedFromItemHrid, count: 1 });
  385. }
  386.  
  387. let totalAskPrice = 0;
  388. let totalBidPrice = 0;
  389. for (let item of inputItems) {
  390. item.name = initData_itemDetailMap[item.itemHrid].name;
  391. item.perAskPrice = jsonObj?.market[item.name]?.ask;
  392. item.perBidPrice = jsonObj?.market[item.name]?.bid;
  393. totalAskPrice += item.perAskPrice * item.count;
  394. totalBidPrice += item.perBidPrice * item.count;
  395. }
  396.  
  397. appendHTMLStr += `<div style="color: DarkGreen; font-size: 10px;">原料价: ${numberFormatter(totalAskPrice)} / ${numberFormatter(totalBidPrice)}</div>`;
  398. for (const item of inputItems) {
  399. appendHTMLStr += `
  400. <div style="color: DarkGreen; font-size: 10px;"> ${item.name} x${item.count}: ${numberFormatter(item.perAskPrice)} / ${numberFormatter(item.perBidPrice)}</div>
  401. `;
  402. }
  403.  
  404. // 基础每小时生产数量
  405. let produceItemPerHour = 3600000 / (initData_actionDetailMap[actionHrid].baseTimeCost / 1000000);
  406. // 基础掉率
  407. let droprate = 1;
  408. // 工具提高速度
  409. let toolPercent = getToolsSpeedBuffByActionHrid(actionHrid);
  410. produceItemPerHour *= 1 + toolPercent / 100;
  411. // 等级碾压提高效率
  412. const requiredLevel = initData_actionDetailMap[actionHrid].levelRequirement.level;
  413. let currentLevel = requiredLevel;
  414. for (const skill of initData_characterSkills) {
  415. if (skill.skillHrid === initData_actionDetailMap[actionHrid].levelRequirement.skillHrid) {
  416. currentLevel = skill.level;
  417. break;
  418. }
  419. }
  420. const levelEffBuff = currentLevel - requiredLevel > 0 ? currentLevel - requiredLevel : 0;
  421. // 房子效率
  422. const houseEffBuff = getHousesEffBuffByActionHrid(actionHrid);
  423. // 茶效率
  424. const teaBuffs = getTeaBuffsByActionHrid(actionHrid);
  425. // 特殊装备效率
  426. const itemEffiBuff = Number(getItemEffiBuffByActionHrid(actionHrid));
  427. // 总效率
  428. produceItemPerHour *= 1 + (levelEffBuff + houseEffBuff + teaBuffs.efficiency + itemEffiBuff) / 100;
  429. // 茶额外数量
  430. let extraQuantityPerHour = (produceItemPerHour * teaBuffs.quantity) / 100;
  431.  
  432. appendHTMLStr += `<div style="color: DarkGreen; font-size: 10px;">生产利润(卖单价进、买单价出;不包括Processing Tea、社区buff、稀有掉落;刷新网页更新人物数据):</div>`;
  433. appendHTMLStr += `<div style="color: DarkGreen; font-size: 10px;">x${droprate}基础掉率 +${toolPercent}%工具速度 +${levelEffBuff}%等级效率 +${houseEffBuff}%房子效率 +${teaBuffs.efficiency}%茶效率 +${itemEffiBuff}%装备效率 +${teaBuffs.quantity}%茶额外数量 +${teaBuffs.lessResource}%茶减少消耗</div>`;
  434. appendHTMLStr += `<div style="color: DarkGreen; font-size: 10px;">每小时生产 ${Number(produceItemPerHour + extraQuantityPerHour).toFixed(1)} 个</div>`;
  435. appendHTMLStr += `<div style="color: DarkGreen;">利润: ${numberFormatter(bid - totalAskPrice * (1 - teaBuffs.lessResource / 100))}/个, ${numberFormatter(
  436. produceItemPerHour * (bid - totalAskPrice * (1 - teaBuffs.lessResource / 100)) + extraQuantityPerHour * bid
  437. )}/小时, ${numberFormatter(24 * (produceItemPerHour * (bid - totalAskPrice * (1 - teaBuffs.lessResource / 100)) + extraQuantityPerHour * bid))}/天</div>`;
  438. } else if (getActionHridFromItemName(itemName) && initData_actionDetailMap[getActionHridFromItemName(itemName)].inputItems === null && initData_actionDetailMap && initData_itemDetailMap) {
  439. // 采集类技能
  440. const actionHrid = getActionHridFromItemName(itemName);
  441. // 基础每小时生产数量
  442. let produceItemPerHour = 3600000 / (initData_actionDetailMap[actionHrid].baseTimeCost / 1000000);
  443. // 基础掉率
  444. let droprate = (initData_actionDetailMap[actionHrid].dropTable[0].minCount + initData_actionDetailMap[actionHrid].dropTable[0].maxCount) / 2;
  445. produceItemPerHour *= droprate;
  446. // 工具提高速度
  447. let toolPercent = getToolsSpeedBuffByActionHrid(actionHrid);
  448. produceItemPerHour *= 1 + toolPercent / 100;
  449. // 等级碾压效率
  450. const requiredLevel = initData_actionDetailMap[actionHrid].levelRequirement.level;
  451. let currentLevel = requiredLevel;
  452. for (const skill of initData_characterSkills) {
  453. if (skill.skillHrid === initData_actionDetailMap[actionHrid].levelRequirement.skillHrid) {
  454. currentLevel = skill.level;
  455. break;
  456. }
  457. }
  458. const levelEffBuff = currentLevel - requiredLevel > 0 ? currentLevel - requiredLevel : 0;
  459. // 房子效率
  460. const houseEffBuff = getHousesEffBuffByActionHrid(actionHrid);
  461. // 茶效率
  462. const teaBuffs = getTeaBuffsByActionHrid(actionHrid);
  463. // 特殊装备效率
  464. const itemEffiBuff = Number(getItemEffiBuffByActionHrid(actionHrid));
  465. // 总效率
  466. produceItemPerHour *= 1 + (levelEffBuff + houseEffBuff + teaBuffs.efficiency + itemEffiBuff) / 100;
  467. // 茶额外数量
  468. let extraQuantityPerHour = (produceItemPerHour * teaBuffs.quantity) / 100;
  469.  
  470. appendHTMLStr += `<div style="color: DarkGreen; font-size: 10px;">生产利润(卖单价进、买单价出;不包括Processing Tea、社区buff、稀有掉落;刷新网页更新人物数据):</div>`;
  471. appendHTMLStr += `<div style="color: DarkGreen; font-size: 10px;">x${droprate}基础掉率 +${toolPercent}%工具速度 +${levelEffBuff}%等级效率 +${houseEffBuff}%房子效率 +${teaBuffs.efficiency}%茶效率 +${itemEffiBuff}%装备效率 +${teaBuffs.quantity}%茶额外数量 +${teaBuffs.lessResource}%茶减少消耗</div>`;
  472. appendHTMLStr += `<div style="color: DarkGreen; font-size: 10px;">每小时生产 ${Number(produceItemPerHour + extraQuantityPerHour).toFixed(1)} 个</div>`;
  473. appendHTMLStr += `<div style="color: DarkGreen;">利润: ${numberFormatter(bid)}/个, ${numberFormatter(produceItemPerHour * bid + extraQuantityPerHour * bid)}/小时, ${numberFormatter(
  474. 24 * (produceItemPerHour * bid + extraQuantityPerHour * bid)
  475. )}/天</div>`;
  476. }
  477.  
  478. insertAfterElem.insertAdjacentHTML("afterend", appendHTMLStr);
  479. }
  480.  
  481. async function fetchMarketJSON(forceFetch = false) {
  482. if (!forceFetch && localStorage.getItem("MWITools_marketAPI_timestamp") && Date.now() - localStorage.getItem("MWITools_marketAPI_timestamp") < 900000) {
  483. return JSON.parse(localStorage.getItem("MWITools_marketAPI_json"));
  484. }
  485.  
  486. console.log("fetchMarketJSON fetch github start");
  487. let jsonStr = null;
  488. jsonStr = await new Promise((resolve, reject) => {
  489. GM.xmlHttpRequest({
  490. url: `https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json`,
  491. method: "GET",
  492. synchronous: true,
  493. timeout: 5000,
  494. onload: async (response) => {
  495. if (response.status == 200) {
  496. console.log("fetchMarketJSON fetch github success 200");
  497. resolve(response.responseText);
  498. } else {
  499. console.error("fetchMarketJSON fetch github onload with HTTP status failure " + response.status);
  500. resolve(null);
  501. }
  502. },
  503. onabort: () => {
  504. console.error("fetchMarketJSON fetch github onabort");
  505. resolve(null);
  506. },
  507. onerror: () => {
  508. console.error("fetchMarketJSON fetch github onerror");
  509. resolve(null);
  510. },
  511. ontimeout: () => {
  512. console.error("fetchMarketJSON fetch github ontimeout");
  513. resolve(null);
  514. },
  515. });
  516. });
  517.  
  518. if (jsonStr === null) {
  519. console.error("fetchMarketJSON network error, using local version");
  520. isUsingLocalMarketJson = true;
  521. jsonStr = MARKET_JSON_LOCAL_BACKUP;
  522. } else {
  523. isUsingLocalMarketJson = false;
  524. }
  525.  
  526. const jsonObj = JSON.parse(jsonStr);
  527. if (jsonObj && jsonObj.time && jsonObj.market) {
  528. jsonObj.market.Coin.ask = 1;
  529. jsonObj.market.Coin.bid = 1;
  530. console.log(jsonObj);
  531. localStorage.setItem("MWITools_marketAPI_timestamp", Date.now());
  532. localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(jsonObj));
  533. return jsonObj;
  534. }
  535. console.error("MWITools: fetchMarketJSON JSON.parse error");
  536. localStorage.setItem("MWITools_marketAPI_timestamp", 0);
  537. localStorage.setItem("MWITools_marketAPI_json", "");
  538. return null;
  539. }
  540.  
  541. function numberFormatter(num, digits = 1) {
  542. if (num === null || num === undefined) {
  543. return null;
  544. }
  545. if (num < 0) {
  546. return "-" + numberFormatter(-num);
  547. }
  548. const lookup = [
  549. { value: 1, symbol: "" },
  550. { value: 1e3, symbol: "k" },
  551. { value: 1e6, symbol: "M" },
  552. { value: 1e9, symbol: "B" },
  553. ];
  554. const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
  555. var item = lookup
  556. .slice()
  557. .reverse()
  558. .find(function (item) {
  559. return num >= item.value;
  560. });
  561. return item ? (num / item.value).toFixed(digits).replace(rx, "$1") + item.symbol : "0";
  562. }
  563.  
  564. function getActionHridFromItemName(name) {
  565. let newName = name.replace("Milk", "Cow");
  566. newName = newName.replace("Log", "Tree");
  567. newName = newName.replace("Cowing", "Milking");
  568. newName = newName.replace("Rainbow Cow", "Unicow");
  569. if (!initData_actionDetailMap) {
  570. console.error("getActionHridFromItemName no initData_actionDetailMap: " + name);
  571. return null;
  572. }
  573. for (const action of Object.values(initData_actionDetailMap)) {
  574. if (action.name === newName) {
  575. return action.hrid;
  576. }
  577. }
  578. return null;
  579. }
  580.  
  581. /* 动作面板 */
  582. const waitForActionPanelParent = () => {
  583. const targetNode = document.querySelector("div.GamePage_mainPanel__2njyb");
  584. if (targetNode) {
  585. console.log("start observe action panel");
  586. const actionPanelObserver = new MutationObserver(async function (mutations) {
  587. for (const mutation of mutations) {
  588. for (const added of mutation.addedNodes) {
  589. if (added && added.classList && added.classList.contains("Modal_modalContainer__3B80m") && added.querySelector("div.SkillActionDetail_nonenhancingComponent__1Y-ZY")) {
  590. handleActionPanel(added.querySelector("div.SkillActionDetail_nonenhancingComponent__1Y-ZY"));
  591. }
  592. }
  593. }
  594. });
  595. actionPanelObserver.observe(targetNode, { attributes: false, childList: true, subtree: true });
  596. } else {
  597. setTimeout(waitForActionPanelParent, 200);
  598. }
  599. };
  600.  
  601. async function handleActionPanel(panel) {
  602. const actionName = panel.querySelector("div.SkillActionDetail_name__3erHV").textContent;
  603. const exp = Number(panel.querySelector("div.SkillActionDetail_expGain__F5xHu").textContent);
  604. const duration = Number(panel.querySelectorAll("div.SkillActionDetail_value__dQjYH")[4].textContent.replace("s", ""));
  605. const inputElem = panel.querySelector("div.SkillActionDetail_maxActionCountInput__1C0Pw input");
  606.  
  607. const actionHrid = initData_actionDetailMap[getActionHridFromItemName(actionName)].hrid;
  608. const effBuff = 1 + getTotalEffiPercentage(actionHrid, false) / 100;
  609.  
  610. // 显示总时间
  611. let hTMLStr = `<div id="showTotalTime" style="color: Green; text-align: left;">${getTotalTimeStr(inputElem.value, duration, effBuff)}</div>`;
  612. inputElem.parentNode.insertAdjacentHTML("afterend", hTMLStr);
  613. const showTotalTimeDiv = panel.querySelector("div#showTotalTime");
  614.  
  615. panel.addEventListener("click", function (evt) {
  616. setTimeout(() => {
  617. showTotalTimeDiv.textContent = getTotalTimeStr(inputElem.value, duration, effBuff);
  618. }, 50);
  619. });
  620. inputElem.addEventListener("keyup", function (evt) {
  621. showTotalTimeDiv.textContent = getTotalTimeStr(inputElem.value, duration, effBuff);
  622. });
  623.  
  624. // 显示快捷按钮
  625. hTMLStr = `<div id="quickInputButtons" style="color: Green; text-align: left;">做 </div>`;
  626. showTotalTimeDiv.insertAdjacentHTML("afterend", hTMLStr);
  627. const quickInputButtonsDiv = panel.querySelector("div#quickInputButtons");
  628.  
  629. const presetHours = [0.5, 1, 2, 3, 4, 5, 6, 10, 12, 24];
  630. for (const value of presetHours) {
  631. const btn = document.createElement("button");
  632. btn.style.backgroundColor = "white";
  633. btn.style.padding = "1px 6px 1px 6px";
  634. btn.style.margin = "1px";
  635. btn.innerText = value === 0.5 ? 0.5 : numberFormatter(value);
  636. btn.onclick = () => {
  637. reactInputTriggerHack(inputElem, Math.round((value * 60 * 60 * effBuff) / duration));
  638. };
  639. quickInputButtonsDiv.append(btn);
  640. }
  641. quickInputButtonsDiv.append(document.createTextNode(" 小时"));
  642.  
  643. quickInputButtonsDiv.append(document.createElement("div"));
  644. quickInputButtonsDiv.append(document.createTextNode("做 "));
  645. const presetTimes = [10, 20, 50, 100, 200, 500, 1000, 2000];
  646. for (const value of presetTimes) {
  647. const btn = document.createElement("button");
  648. btn.style.backgroundColor = "white";
  649. btn.style.padding = "1px 6px 1px 6px";
  650. btn.style.margin = "1px";
  651. btn.innerText = numberFormatter(value);
  652. btn.onclick = () => {
  653. reactInputTriggerHack(inputElem, value);
  654. };
  655. quickInputButtonsDiv.append(btn);
  656. }
  657. quickInputButtonsDiv.append(document.createTextNode(" 次"));
  658.  
  659. // 还有多久到多少技能等级
  660. const skillHrid = initData_actionDetailMap[getActionHridFromItemName(actionName)].experienceGain.skillHrid;
  661. let currentExp = null;
  662. let currentLevel = null;
  663. for (const skill of initData_characterSkills) {
  664. if (skill.skillHrid === skillHrid) {
  665. currentExp = skill.experience;
  666. currentLevel = skill.level;
  667. break;
  668. }
  669. }
  670. if (currentExp && currentLevel) {
  671. const calculateNeedToLevel = (currentLevel, targetLevel, effBuff, duration, exp) => {
  672. let needTotalTimeSec = 0;
  673. let needTotalNumOfActions = 0;
  674. for (let level = currentLevel; level < targetLevel; level++) {
  675. let needExpToNextLevel = null;
  676. if (level === currentLevel) {
  677. needExpToNextLevel = initData_levelExperienceTable[level + 1] - currentExp;
  678. } else {
  679. needExpToNextLevel = initData_levelExperienceTable[level + 1] - initData_levelExperienceTable[level];
  680. }
  681. const extraLevelEffBuff = (level - currentLevel) * 0.01; // 升级过程中,每升一级,额外多1%效率
  682. const needNumOfActionsToNextLevel = Math.round(needExpToNextLevel / exp);
  683. needTotalNumOfActions += needNumOfActionsToNextLevel;
  684. needTotalTimeSec += (needNumOfActionsToNextLevel / (effBuff + extraLevelEffBuff)) * duration;
  685. }
  686. return { numOfActions: needTotalNumOfActions, timeSec: needTotalTimeSec };
  687. };
  688.  
  689. const need = calculateNeedToLevel(currentLevel, currentLevel + 1, effBuff, duration, exp);
  690. hTMLStr = `<div id="tillLevel" style="color: Green; text-align: left;">到 <input id="tillLevelInput" type="number" value="${currentLevel + 1}" min="${
  691. currentLevel + 1
  692. }" max="200"> 级还需做 <span id="tillLevelNumber">${need.numOfActions} 次[${timeReadable(need.timeSec)}] (刷新网页更新当前等级)</span></div>`;
  693.  
  694. quickInputButtonsDiv.insertAdjacentHTML("afterend", hTMLStr);
  695. const tillLevelInput = panel.querySelector("input#tillLevelInput");
  696. const tillLevelNumber = panel.querySelector("span#tillLevelNumber");
  697. tillLevelInput.onchange = () => {
  698. const targetLevel = Number(tillLevelInput.value);
  699. if (targetLevel > currentLevel && targetLevel <= 200) {
  700. const need = calculateNeedToLevel(currentLevel, targetLevel, effBuff, duration, exp);
  701. tillLevelNumber.textContent = `${need.numOfActions} 次[${timeReadable(need.timeSec)}] (刷新网页更新当前等级)`;
  702. } else {
  703. tillLevelNumber.textContent = "Error";
  704. }
  705. };
  706. tillLevelInput.addEventListener("keyup", function (evt) {
  707. const targetLevel = Number(tillLevelInput.value);
  708. if (targetLevel > currentLevel && targetLevel <= 200) {
  709. const need = calculateNeedToLevel(currentLevel, targetLevel, effBuff, duration, exp);
  710. tillLevelNumber.textContent = `${need.numOfActions} 次[${timeReadable(need.timeSec)}] (刷新网页更新当前等级)`;
  711. } else {
  712. tillLevelNumber.textContent = "Error";
  713. }
  714. });
  715. }
  716.  
  717. // 显示每小时经验
  718. panel
  719. .querySelector("div#tillLevel")
  720. .insertAdjacentHTML(
  721. "afterend",
  722. `<div id="expPerHour" style="color: Green; text-align: left;">每小时经验: ${numberFormatter(Math.round((3600 / duration) * exp * effBuff))} (+${Number((effBuff - 1) * 100).toFixed(
  723. 1
  724. )}%效率)</div>`
  725. );
  726.  
  727. // 显示Foraging最后一个图综合收益
  728. if (panel.querySelector("div.SkillActionDetail_dropTable__3ViVp").children.length > 1) {
  729. const jsonObj = await fetchMarketJSON();
  730. const actionHrid = "/actions/foraging/" + actionName.toLowerCase().replaceAll(" ", "_");
  731. let numOfActionsPerHour = 3600000 / (initData_actionDetailMap[actionHrid].baseTimeCost / 1000000);
  732. let dropTable = initData_actionDetailMap[actionHrid].dropTable;
  733. let virtualItemBid = 0;
  734. for (const drop of dropTable) {
  735. const bid = jsonObj?.market[initData_itemDetailMap[drop.itemHrid].name]?.bid;
  736. const amount = drop.dropRate * ((drop.minCount + drop.maxCount) / 2);
  737. virtualItemBid += bid * amount;
  738. }
  739.  
  740. // 工具提高速度
  741. let toolPercent = getToolsSpeedBuffByActionHrid(actionHrid);
  742. numOfActionsPerHour *= 1 + toolPercent / 100;
  743. // 等级碾压效率
  744. const requiredLevel = initData_actionDetailMap[actionHrid].levelRequirement.level;
  745. let currentLevel = requiredLevel;
  746. for (const skill of initData_characterSkills) {
  747. if (skill.skillHrid === initData_actionDetailMap[actionHrid].levelRequirement.skillHrid) {
  748. currentLevel = skill.level;
  749. break;
  750. }
  751. }
  752. const levelEffBuff = currentLevel - requiredLevel;
  753. // 房子效率
  754. const houseEffBuff = getHousesEffBuffByActionHrid(actionHrid);
  755. // 茶
  756. const teaBuffs = getTeaBuffsByActionHrid(actionHrid);
  757. // 总效率
  758. numOfActionsPerHour *= 1 + (levelEffBuff + houseEffBuff + teaBuffs.efficiency) / 100;
  759. // 茶额外数量
  760. let extraQuantityPerHour = (numOfActionsPerHour * teaBuffs.quantity) / 100;
  761.  
  762. let htmlStr = `<div id="totalProfit" style="color: Green; text-align: left;">综合利润: ${numberFormatter(
  763. numOfActionsPerHour * virtualItemBid + extraQuantityPerHour * virtualItemBid
  764. )}/小时, ${numberFormatter(24 * numOfActionsPerHour * virtualItemBid + extraQuantityPerHour * virtualItemBid)}/天</div>`;
  765. panel.querySelector("div#expPerHour").insertAdjacentHTML("afterend", htmlStr);
  766. }
  767. }
  768.  
  769. function getTotalEffiPercentage(actionHrid, debug = false) {
  770. if (debug) {
  771. console.log("----- getTotalEffiPercentage " + actionHrid);
  772. }
  773. // 等级碾压效率
  774. const requiredLevel = initData_actionDetailMap[actionHrid].levelRequirement.level;
  775. let currentLevel = requiredLevel;
  776. for (const skill of initData_characterSkills) {
  777. if (skill.skillHrid === initData_actionDetailMap[actionHrid].levelRequirement.skillHrid) {
  778. currentLevel = skill.level;
  779. break;
  780. }
  781. }
  782. const levelEffBuff = currentLevel - requiredLevel > 0 ? currentLevel - requiredLevel : 0;
  783. if (debug) {
  784. console.log("等级碾压 " + levelEffBuff);
  785. }
  786. // 房子效率
  787. const houseEffBuff = getHousesEffBuffByActionHrid(actionHrid);
  788. if (debug) {
  789. console.log("房子 " + houseEffBuff);
  790. }
  791. // 茶
  792. const teaBuffs = getTeaBuffsByActionHrid(actionHrid);
  793. if (debug) {
  794. console.log("茶 " + teaBuffs.efficiency);
  795. }
  796. // 特殊装备
  797. const itemEffiBuff = getItemEffiBuffByActionHrid(actionHrid);
  798. if (debug) {
  799. console.log("特殊装备 " + itemEffiBuff);
  800. }
  801. // 总效率
  802. const total = levelEffBuff + houseEffBuff + teaBuffs.efficiency + Number(itemEffiBuff);
  803. if (debug) {
  804. console.log("总计 " + total);
  805. }
  806. return total;
  807. }
  808.  
  809. function getTotalTimeStr(input, duration, effBuff) {
  810. if (input === "unlimited") {
  811. return "[∞]";
  812. } else if (isNaN(input)) {
  813. return "Error";
  814. }
  815. return "[" + timeReadable(Math.round(input / effBuff) * duration) + "]";
  816. }
  817.  
  818. function reactInputTriggerHack(inputElem, value) {
  819. let lastValue = inputElem.value;
  820. inputElem.value = value;
  821. let event = new Event("input", { bubbles: true });
  822. event.simulated = true;
  823. let tracker = inputElem._valueTracker;
  824. if (tracker) {
  825. tracker.setValue(lastValue);
  826. }
  827. inputElem.dispatchEvent(event);
  828. }
  829.  
  830. /* 左侧栏显示技能百分比 */
  831. const waitForProgressBar = () => {
  832. const elements = document.querySelectorAll(".NavigationBar_currentExperience__3GDeX");
  833. if (elements.length) {
  834. removeInsertedDivs();
  835. elements.forEach((element) => {
  836. let text = element.style.width;
  837. text = Number(text.replace("%", "")).toFixed(2) + "%";
  838.  
  839. const span = document.createElement("span");
  840. span.textContent = text;
  841. span.classList.add("insertedSpan");
  842. span.style.fontSize = "13px";
  843. span.style.color = "green";
  844.  
  845. element.parentNode.parentNode.querySelector("span.NavigationBar_level__3C7eR").style.width = "auto";
  846.  
  847. const insertParent = element.parentNode.parentNode.children[0];
  848. insertParent.insertBefore(span, insertParent.children[1]);
  849. });
  850. } else {
  851. setTimeout(waitForProgressBar, 200);
  852. }
  853. };
  854.  
  855. const removeInsertedDivs = () => document.querySelectorAll("span.insertedSpan").forEach((div) => div.parentNode.removeChild(div));
  856.  
  857. window.setInterval(() => {
  858. removeInsertedDivs();
  859. waitForProgressBar();
  860. }, 1000);
  861.  
  862. /* 战斗总结 */
  863. async function handleBattleSummary(message) {
  864. const marketJson = await fetchMarketJSON();
  865. let hasMarketJson = true;
  866. if (!marketJson) {
  867. console.error("handleBattleSummary null marketAPI");
  868. hasMarketJson = false;
  869. }
  870. let totalPriceAsk = 0;
  871. let totalPriceAskBid = 0;
  872.  
  873. if (hasMarketJson) {
  874. for (const loot of Object.values(message.unit.totalLootMap)) {
  875. const itemName = initData_itemDetailMap[loot.itemHrid].name;
  876. const itemCount = loot.count;
  877. if (marketJson.market[itemName]) {
  878. totalPriceAsk += marketJson.market[itemName].ask * itemCount;
  879. totalPriceAskBid += marketJson.market[itemName].bid * itemCount;
  880. } else {
  881. console.error("handleBattleSummary failed to read price of " + loot.itemHrid);
  882. }
  883. }
  884. }
  885.  
  886. let totalSkillsExp = 0;
  887. for (const exp of Object.values(message.unit.totalSkillExperienceMap)) {
  888. totalSkillsExp += exp;
  889. }
  890.  
  891. let tryTimes = 0;
  892. findElem();
  893. function findElem() {
  894. tryTimes++;
  895. let elem = document.querySelector(".BattlePanel_gainedExp__3SaCa");
  896. if (elem) {
  897. // 战斗时长和次数
  898. let battleDurationSec = null;
  899. const combatInfoElement = document.querySelector(".BattlePanel_combatInfo__sHGCe");
  900. if (combatInfoElement) {
  901. let matches = combatInfoElement.innerHTML.match(
  902. /(战斗时长|Combat Duration): (?:(\d+)d\s*)?(?:(\d+)h\s*)?(?:(\d+)m\s*)?(?:(\d+)s).*?(战斗|Battles): (\d+).*?(死亡次数|Deaths): (\d+)/
  903. );
  904. if (matches) {
  905. let days = parseInt(matches[2], 10) || 0;
  906. let hours = parseInt(matches[3], 10) || 0;
  907. let minutes = parseInt(matches[4], 10) || 0;
  908. let seconds = parseInt(matches[5], 10) || 0;
  909. let battles = parseInt(matches[7], 10);
  910. battleDurationSec = days * 86400 + hours * 3600 + minutes * 60 + seconds;
  911. let efficiencyPerHour = ((battles / battleDurationSec) * 3600).toFixed(1);
  912. elem.insertAdjacentHTML("afterend", `<div id="script_battleNumbers" style="color: Green;">平均每小时战斗 ${efficiencyPerHour} 次</div>`);
  913. }
  914. }
  915. // 总收入
  916. document
  917. .querySelector("div#script_battleNumbers")
  918. .insertAdjacentHTML("afterend", `<div id="script_totalIncome" style="color: Green;">总收入: ${numberFormatter(totalPriceAsk)} / ${numberFormatter(totalPriceAskBid)}</div>`);
  919. // 平均收入
  920. if (battleDurationSec) {
  921. document
  922. .querySelector("div#script_totalIncome")
  923. .insertAdjacentHTML(
  924. "afterend",
  925. `<div id="script_averageIncome" style="color: Green;">平均每小时收入: ${numberFormatter(totalPriceAsk / (battleDurationSec / 60 / 60))} / ${numberFormatter(
  926. totalPriceAskBid / (battleDurationSec / 60 / 60)
  927. )}</div>`
  928. );
  929. } else {
  930. console.error("handleBattleSummary unable to display average income due to null battleDurationSec");
  931. }
  932. // 总经验
  933. document
  934. .querySelector("div#script_averageIncome")
  935. .insertAdjacentHTML("afterend", `<div id="script_totalSkillsExp" style="color: Green;">总经验: ${numberFormatter(totalSkillsExp)}</div>`);
  936. // 平均经验
  937. if (battleDurationSec) {
  938. document
  939. .querySelector("div#script_totalSkillsExp")
  940. .insertAdjacentHTML(
  941. "afterend",
  942. `<div id="script_averageSkillsExp" style="color: Green;">平均每小时经验: ${numberFormatter(totalSkillsExp / (battleDurationSec / 60 / 60))}</div>`
  943. );
  944. } else {
  945. console.error("handleBattleSummary unable to display average exp due to null battleDurationSec");
  946. }
  947. } else if (tryTimes <= 10) {
  948. setTimeout(findElem, 200);
  949. } else {
  950. console.error("handleBattleSummary: Elem not found after 10 tries.");
  951. }
  952. }
  953. }
  954.  
  955. /* 图标上显示装备等级 */
  956. async function addItemLevels() {
  957. const iconDivs = document.querySelectorAll("div.Item_itemContainer__x7kH1 div.Item_item__2De2O.Item_clickable__3viV6");
  958. for (const div of iconDivs) {
  959. if (div.querySelector("div.Item_name__2C42x")) {
  960. continue;
  961. }
  962. const href = div.querySelector("use").getAttribute("href");
  963. const hrefName = href.split("#")[1];
  964. const itemHrid = "/items/" + hrefName;
  965. const itemLevel = initData_itemDetailMap[itemHrid]?.itemLevel;
  966. const itemAbilityLevel = initData_itemDetailMap[itemHrid]?.abilityBookDetail?.levelRequirements?.[0]?.level;
  967. if (itemLevel && itemLevel > 0) {
  968. if (!div.querySelector("div.script_itemLevel")) {
  969. div.insertAdjacentHTML(
  970. "beforeend",
  971. `<div class="script_itemLevel" style="z-index: 1; position: absolute; top: 2px; right: 2px; text-align: right; color: green;">${itemLevel}</div>`
  972. );
  973. }
  974. } else if (itemAbilityLevel && itemAbilityLevel > 0) {
  975. if (!div.querySelector("div.script_itemLevel")) {
  976. div.insertAdjacentHTML(
  977. "beforeend",
  978. `<div class="script_itemLevel" style="z-index: 1; position: absolute; top: 2px; right: 2px; text-align: right; color: green;">${itemAbilityLevel}</div>`
  979. );
  980. }
  981. }
  982. }
  983. }
  984. setInterval(addItemLevels, 500);
  985. })();