Milky Way Idle - Loot Drops Value Add-on

add-on to Loot Drops Overlay

当前为 2025-05-07 提交的版本,查看 最新版本

// ==UserScript==
// @name         Milky Way Idle - Loot Drops Value Add-on
// @namespace    https://milkywayidle.com/
// @version      1.0
// @description  add-on to Loot Drops Overlay
// @author       notawhale
// @match        https://www.milkywayidle.com/*
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const CUSTOM_SORT_KEY = 'lootDropsCustomSortPref';
  const SORT_MODES = ['name', 'value', 'quantity'];
  const SORT_LABELS = {
    name: 'Name',
    value: 'Value',
    quantity: 'Quantity'
  };

  const ENABLE_LOG = true;
  let marketData = null;

  const fixedValueChests = new Set([
    'small treasure chest',
    'medium treasure chest',
    'large treasure chest',
    'chimerical chest',
    'sinister chest',
    'enchanted chest',
    'pirate chest'
  ]);

  function formatCoins(value) {
    return `${Math.round(value).toLocaleString()} coin`;
  }

  function capitalizeEachWord(str) {
    return str
      .split(' ')
      .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
      .join(' ');
  }

  async function loadMarketData() {
    try {
      const res = await fetch(
        'https://raw.githubusercontent.com/holychikenz/MWIApi/main/medianmarket.json'
      );
      const json = await res.json();
      marketData = json.market || {};
      console.log('[ValueAdd] Market data loaded.');
    } catch (e) {
      console.error('[ValueAdd] Failed to load market data:', e);
    }
  }

  function getUnitValue(name) {
    const normalized = name.trim().toLowerCase();
    if (fixedValueChests.has(normalized)) return 1;
    if (normalized === 'coin') return 1;
    const entry = marketData[capitalizeEachWord(name)];
    return entry?.ask || 0;
  }

  function calculateItemValue(name, count) {
    return count * getUnitValue(name);
  }

  function calculateTotalValue(section, playerName = 'Player') {
    const itemRows = section.querySelectorAll('.ldt-loot-item-entry');
    let total = 0;

    if (ENABLE_LOG) console.group(`[${playerName}] Value Breakdown`);

    itemRows.forEach((row) => {
      const nameEl = row.querySelector('.ldt-item-name');
      const countEl = row.querySelector('.ldt-item-count');
      if (!nameEl || !countEl) return;

      const name = nameEl.textContent.trim();
      let rawCount = countEl.dataset.count;
      if (!rawCount) {
        const parsed = parseInt(countEl.textContent.replace(/[^\d]/g, ''), 10);
        rawCount = isNaN(parsed) ? '0' : String(parsed);
        countEl.dataset.count = rawCount;
      }
      const count = parseInt(rawCount, 10);
      const unit = getUnitValue(name);
      const itemTotal = count * unit;
      total += itemTotal;

      if (ENABLE_LOG) {
        console.log(
          `• ${name}: count=${count}, unit=${formatCoins(unit)}, total=${formatCoins(itemTotal)}`
        );
      }
    });

    if (ENABLE_LOG) {
      console.log(`= Total: ${formatCoins(total)}`);
      console.groupEnd();
    }

    return total;
  }

  function injectValuesAndSort() {
    const sortPref = localStorage.getItem(CUSTOM_SORT_KEY) || 'name';
    const playerSections = document.querySelectorAll('.ldt-player-stats-section');

    playerSections.forEach((section) => {
      const header = section.querySelector('.ldt-player-name-header');
      const playerName = header?.textContent?.split('(')[0]?.trim() || 'Unknown';

      const existingSpan = header.querySelector('span[data-value-injected]');
      if (existingSpan) existingSpan.remove();

      const totalValue = calculateTotalValue(section, playerName);
      if (totalValue > 0) {
        const span = document.createElement('span');
        span.style.color = 'gold';
        span.style.fontWeight = 'normal';
        span.style.fontSize = '0.9em';
        span.textContent = ` (${formatCoins(totalValue)})`;
        span.dataset.valueInjected = 'true';
        header.appendChild(span);
      }

      const list = section.querySelector('.ldt-loot-list');
      if (!list) return;

      const items = Array.from(list.querySelectorAll('.ldt-loot-item-entry'));
      const itemData = items.map((row) => {
        const nameEl = row.querySelector('.ldt-item-name');
        const countEl = row.querySelector('.ldt-item-count');
        const name = nameEl?.textContent.trim() || '';

        let rawCount = countEl.dataset.count;
        if (!rawCount) {
          const parsed = parseInt(countEl.textContent.replace(/[^\d]/g, ''), 10);
          rawCount = isNaN(parsed) ? '0' : String(parsed);
          countEl.dataset.count = rawCount;
        }

        const count = parseInt(rawCount, 10);
        const value = calculateItemValue(name, count);
        return { row, name, count, value, nameEl, countEl };
      });

      itemData.sort((a, b) => {
        if (sortPref === 'value') {
          return b.value - a.value || b.count - a.count || a.name.localeCompare(b.name);
        }
        if (sortPref === 'quantity') {
          return b.count - a.count || a.name.localeCompare(b.name);
        }
        return a.name.localeCompare(b.name);
      });

      itemData.forEach(({ countEl, count }) => {
        countEl.textContent = `× ${count}`;
      });

      list.innerHTML = '';
      itemData.forEach(({ row }) => list.appendChild(row));
    });
  }

  function overrideSortButton() {
    const btn = document.querySelector('#milt-loot-drops-display-sortbtn');
    if (!btn || btn.dataset.customOverrideInjected) return;

    btn.dataset.customOverrideInjected = 'true';

    const updateLabel = () => {
      const current = localStorage.getItem(CUSTOM_SORT_KEY) || 'name';
      btn.textContent = `Sort: ${SORT_LABELS[current]}`;
    };

    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      const current = localStorage.getItem(CUSTOM_SORT_KEY) || 'name';
      const next =
        SORT_MODES[(SORT_MODES.indexOf(current) + 1) % SORT_MODES.length];
      localStorage.setItem(CUSTOM_SORT_KEY, next);
      updateLabel();
      injectValuesAndSort();
    });

    updateLabel();
  }

  async function main() {
    await loadMarketData();

    setInterval(() => {
      const overlay = document.getElementById('milt-loot-drops-display');
      if (!overlay || overlay.classList.contains('is-hidden')) return;

      overrideSortButton();
      injectValuesAndSort();
    }, 1000);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', main);
  } else {
    main();
  }
})();