您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Calculate combat profit with uniform midas/extractor options.
当前为
// ==UserScript== // @name Elethor Combat Calculator // @namespace http://tampermonkey.net/ // @version 1.11 // @description Calculate combat profit with uniform midas/extractor options. // @author Eugene // @match https://elethor.com/* // @grant GM_xmlhttpRequest // @esversion 11 // @license GPL-3.0-or-later // ==/UserScript== /* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ (function() { 'use strict'; const monsterData = { 'Apex Nassul': { mobDrop: 'Ether Flux', itemId: 381, baseMobDropChance: 40.5, baseGoldDropChance: 100 }, 'Scarlet Merchant': { mobDrop: null, itemId: null, baseMobDropChance: 0, baseGoldDropChance: 100 }, 'Voidkin Artificier': { mobDrop: 'Void Artifact', itemId: 385, baseMobDropChance: 100, baseGoldDropChance: 100 }, 'Voidstalker': { mobDrop: 'Tattered Cowl', itemId: 386, baseMobDropChance: 40.5, baseGoldDropChance: 100 }, 'Tunnel Ambusher': { mobDrop: 'Carapace Segment', itemId: 387, baseMobDropChance: 40.5, baseGoldDropChance: 100 }, 'Elite Guard Broodmother': { mobDrop: 'Elite Guard Insignia', itemId: 388, baseMobDropChance: 40.5, baseGoldDropChance: 100 } }; function createButton() { const button = document.createElement('button'); button.textContent = 'Combat Calculator'; button.style.cssText = "position: fixed; top: 20px; right: 20px; background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; z-index: 1000;"; document.body.appendChild(button); button.addEventListener('click', toggleCalculator); } function createCalculatorUI() { const container = document.createElement('div'); container.id = 'combatCalculator'; container.style.cssText = "display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: black; color: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(255,255,255,0.5); z-index: 1001; max-height: 80vh; overflow-y: auto; min-width: 800px;"; const title = document.createElement('h2'); title.textContent = 'Combat Calculator'; title.style.textAlign = 'center'; const subtitle = document.createElement('h3'); subtitle.style.textAlign = 'center'; subtitle.innerHTML = 'Made by <a href="https://elethor.com/profile/49979" target="_blank">Eugene</a>'; container.appendChild(title); container.appendChild(subtitle); // -- Uniform Settings Container -- const uniformContainer = document.createElement('div'); uniformContainer.id = 'uniformContainer'; uniformContainer.style.cssText = "margin: 10px 0; padding: 10px; border: 1px solid white;"; // Uniform Midas Checkbox const uniformMidasLabel = document.createElement('label'); uniformMidasLabel.style.marginRight = '20px'; const uniformMidasCheckbox = document.createElement('input'); uniformMidasCheckbox.type = 'checkbox'; uniformMidasCheckbox.id = 'uniformMidasCheckbox'; uniformMidasLabel.appendChild(uniformMidasCheckbox); uniformMidasLabel.appendChild(document.createTextNode(' Uniform Midas')); uniformMidasCheckbox.addEventListener('change', function() { document.querySelectorAll('.midas-input').forEach(input => { input.disabled = this.checked; input.parentElement.classList.toggle('disabled-input', this.checked); }); }); uniformContainer.appendChild(uniformMidasLabel); // Uniform Extractor Checkbox const uniformExtractorLabel = document.createElement('label'); const uniformExtractorCheckbox = document.createElement('input'); uniformExtractorCheckbox.type = 'checkbox'; uniformExtractorCheckbox.id = 'uniformExtractorCheckbox'; uniformExtractorLabel.appendChild(uniformExtractorCheckbox); uniformExtractorLabel.appendChild(document.createTextNode(' Uniform Extractor')); uniformExtractorCheckbox.addEventListener('change', function() { document.querySelectorAll('.extractor-input').forEach(input => { input.disabled = this.checked; input.parentElement.classList.toggle('disabled-input', this.checked); }); }); uniformContainer.appendChild(uniformExtractorLabel); // Global Midas Input (hidden by default) const globalMidasContainer = document.createElement('div'); globalMidasContainer.id = 'globalMidasContainer'; globalMidasContainer.style.cssText = "display: none; margin-top: 10px;"; const globalMidasLabel = document.createElement('label'); globalMidasLabel.textContent = 'Global Midas %: '; const globalMidasInput = document.createElement('input'); globalMidasInput.type = 'number'; globalMidasInput.id = 'globalMidasInput'; globalMidasInput.min = '0'; globalMidasInput.style.cssText = "background-color: white; color: black;"; globalMidasContainer.appendChild(globalMidasLabel); globalMidasContainer.appendChild(globalMidasInput); uniformContainer.appendChild(globalMidasContainer); // Global Extractor Input (hidden by default) const globalExtractorContainer = document.createElement('div'); globalExtractorContainer.id = 'globalExtractorContainer'; globalExtractorContainer.style.cssText = "display: none; margin-top: 10px;"; const globalExtractorLabel = document.createElement('label'); globalExtractorLabel.textContent = 'Global Extractor %: '; const globalExtractorInput = document.createElement('input'); globalExtractorInput.type = 'number'; globalExtractorInput.id = 'globalExtractorInput'; globalExtractorInput.min = '0'; globalExtractorInput.style.cssText = "background-color: white; color: black;"; globalExtractorContainer.appendChild(globalExtractorLabel); globalExtractorContainer.appendChild(globalExtractorInput); uniformContainer.appendChild(globalExtractorContainer); // Event listeners for uniform checkboxes uniformMidasCheckbox.addEventListener('change', function() { if (this.checked) { globalMidasContainer.style.display = 'block'; // Disable individual midas inputs document.querySelectorAll('.midas-input').forEach(input => { input.disabled = true; input.style.backgroundColor = '#f0f0f0'; }); } else { globalMidasContainer.style.display = 'none'; // Enable individual midas inputs document.querySelectorAll('.midas-input').forEach(input => { input.disabled = false; input.style.backgroundColor = 'white'; }); } }); uniformExtractorCheckbox.addEventListener('change', function() { if (this.checked) { globalExtractorContainer.style.display = 'block'; // Disable individual extractor inputs document.querySelectorAll('.extractor-input').forEach(input => { input.disabled = true; input.style.backgroundColor = '#f0f0f0'; }); } else { globalExtractorContainer.style.display = 'none'; document.querySelectorAll('.extractor-input').forEach(input => { input.disabled = false; input.style.backgroundColor = 'white'; }); } }); container.appendChild(uniformContainer); // ------------------------------- const monsterList = document.createElement('div'); monsterList.style.marginTop = '20px'; for (const monster in monsterData) { const row = createMonsterRow(monster, monsterData[monster]); monsterList.appendChild(row); } const calculateButton = document.createElement('button'); calculateButton.textContent = 'Calculate'; calculateButton.style.cssText = "background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 20px; width: 100%;"; calculateButton.addEventListener('click', calculateGpA); container.appendChild(monsterList); container.appendChild(calculateButton); document.body.appendChild(container); // Add overlay const overlay = document.createElement('div'); overlay.id = 'calculatorOverlay'; overlay.style.cssText = "display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 1000;"; overlay.addEventListener('click', toggleCalculator); document.body.appendChild(overlay); } function createMonsterRow(monsterName, data) { const row = document.createElement('div'); // Add a class to easily identify monster rows row.classList.add('monster-row'); row.style.cssText = "display: grid; grid-template-columns: 200px repeat(6, 1fr) 100px; gap: 10px; margin-bottom: 15px; align-items: center; color: white;"; // Monster name const nameLabel = document.createElement('div'); nameLabel.textContent = monsterName; row.appendChild(nameLabel); // Tier input const tierInput = document.createElement('input'); tierInput.type = 'number'; tierInput.min = '0'; tierInput.max = '10000'; tierInput.placeholder = 'Tier'; tierInput.className = 'tier-input'; tierInput.style.backgroundColor = 'white'; tierInput.style.color = 'black'; row.appendChild(tierInput); // Win rate input const winRateInput = document.createElement('input'); winRateInput.type = 'number'; winRateInput.min = '0'; winRateInput.max = '100'; winRateInput.placeholder = 'Win Rate %'; winRateInput.className = 'winrate-input'; winRateInput.style.backgroundColor = 'white'; winRateInput.style.color = 'black'; row.appendChild(winRateInput); // Price radio buttons const priceContainer = document.createElement('div'); priceContainer.style.cssText = "display: flex; align-items: center; gap: 10px;"; const priceLabel = document.createElement('span'); priceLabel.textContent = 'Price: '; priceContainer.appendChild(priceLabel); const priceTypes = ['Buy price', 'Sell price', 'Manual']; priceTypes.forEach((type, index) => { const radioContainer = document.createElement('div'); radioContainer.style.cssText = "display: flex; align-items: center; gap: 4px;"; const radio = document.createElement('input'); radio.type = 'radio'; radio.name = `price-${monsterName}`; radio.value = type.toLowerCase(); radio.id = `${type.toLowerCase()}-${monsterName}`; radio.style.margin = '0'; if (index === 0) radio.checked = true; const label = document.createElement('label'); label.htmlFor = radio.id; label.textContent = type; label.style.margin = '0'; radioContainer.appendChild(radio); radioContainer.appendChild(label); priceContainer.appendChild(radioContainer); }); // Disable price options for Scarlet Merchant if (monsterName === 'Scarlet Merchant') { priceContainer.querySelectorAll('input[type="radio"]').forEach(radio => { radio.disabled = true; }); } row.appendChild(priceContainer); // Price input - always visible and persistent const priceInput = document.createElement('input'); priceInput.type = 'number'; priceInput.min = '0'; priceInput.placeholder = monsterName === 'Scarlet Merchant' ? 'No mob drops' : 'Price'; priceInput.className = 'price-input'; priceInput.dataset.monster = monsterName; priceInput.style.backgroundColor = monsterName === 'Scarlet Merchant' ? '#f0f0f0' : 'white'; priceInput.style.color = 'black'; if (monsterName === 'Scarlet Merchant') { priceInput.disabled = true; } row.appendChild(priceInput); // Load saved values from localStorage if available const savedManualPrice = localStorage.getItem(`manual-price-${monsterName}`); const savedTier = localStorage.getItem(`tier-${monsterName}`); const savedWinRate = localStorage.getItem(`winrate-${monsterName}`); if (savedManualPrice) { priceInput.value = savedManualPrice; } if (savedTier) { tierInput.value = savedTier; } if (savedWinRate) { winRateInput.value = savedWinRate; } // Save tier and winrate to localStorage when they change tierInput.addEventListener('input', () => { localStorage.setItem(`tier-${monsterName}`, tierInput.value); }); winRateInput.addEventListener('input', () => { localStorage.setItem(`winrate-${monsterName}`, winRateInput.value); }); // Save manual price to localStorage when it changes priceInput.addEventListener('input', () => { if (priceContainer.querySelector('input[value="manual"]:checked')) { localStorage.setItem(`manual-price-${monsterName}`, priceInput.value); } }); // Handle price input behavior based on radio selection priceContainer.querySelectorAll('input[type="radio"]').forEach(radio => { // Load saved price type selection from localStorage const savedPriceType = localStorage.getItem(`price-type-${monsterName}`); if (savedPriceType && radio.value === savedPriceType) { radio.checked = true; } radio.addEventListener('change', () => { // Save price type selection to localStorage localStorage.setItem(`price-type-${monsterName}`, radio.value); if (radio.value === 'manual') { priceInput.disabled = false; priceInput.style.backgroundColor = 'white'; priceInput.placeholder = 'Enter price'; // Restore saved manual price if available const savedPrice = localStorage.getItem(`manual-price-${monsterName}`); if (savedPrice) { priceInput.value = savedPrice; } } else { priceInput.disabled = true; priceInput.style.backgroundColor = '#f0f0f0'; priceInput.placeholder = 'Fetching price...'; // If monster has an item, fetch the price immediately if (data.itemId) { fetchMarketData(data.itemId, radio.value === 'buy price' ? 'buy' : 'sell') .then(price => { priceInput.value = price; }) .catch(error => { console.error(`Error fetching price for ${monsterName}:`, error); priceInput.placeholder = 'Error fetching price'; }); } } }); }); // Set initial state based on saved or default selection const selectedRadio = priceContainer.querySelector('input[type="radio"]:checked'); if (selectedRadio.value === 'manual') { priceInput.disabled = false; priceInput.style.backgroundColor = 'white'; priceInput.placeholder = 'Enter price'; } else { priceInput.disabled = true; priceInput.style.backgroundColor = '#f0f0f0'; priceInput.placeholder = 'Fetching price...'; // If monster has an item, fetch the price immediately if (data.itemId) { fetchMarketData(data.itemId, selectedRadio.value === 'buy price' ? 'buy' : 'sell') .then(price => { priceInput.value = price; }) .catch(error => { console.error(`Error fetching price for ${monsterName}:`, error); priceInput.placeholder = 'Error fetching price'; }); } } // Midas percentage input const midasInput = document.createElement('input'); midasInput.type = 'number'; midasInput.min = '0'; midasInput.placeholder = 'Midas %'; midasInput.className = 'midas-input'; midasInput.style.backgroundColor = 'white'; midasInput.style.color = 'black'; row.appendChild(midasInput); // Load saved Midas value const savedMidas = localStorage.getItem(`midas-${monsterName}`); if (savedMidas) { midasInput.value = savedMidas; } // Save Midas value when it changes midasInput.addEventListener('input', () => { localStorage.setItem(`midas-${monsterName}`, midasInput.value); }); // Extractor percentage input const extractorInput = document.createElement('input'); extractorInput.type = 'number'; extractorInput.min = '0'; extractorInput.placeholder = 'Extractor %'; extractorInput.className = 'extractor-input'; extractorInput.style.backgroundColor = 'white'; extractorInput.style.color = 'black'; row.appendChild(extractorInput); // Load saved Extractor value const savedExtractor = localStorage.getItem(`extractor-${monsterName}`); if (savedExtractor) { extractorInput.value = savedExtractor; } // Save Extractor value when it changes extractorInput.addEventListener('input', () => { localStorage.setItem(`extractor-${monsterName}`, extractorInput.value); }); // GpA result const gpaResult = document.createElement('div'); gpaResult.className = 'gpa-result'; row.appendChild(gpaResult); return row; } async function fetchMarketData(itemId, priceType) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `https://elethor.com/game/market/listings?itemId=${itemId}`, onload: function(response) { try { const data = JSON.parse(response.responseText); if (!data || !Array.isArray(data)) { throw new Error('Invalid market data received'); } const relevantListings = data.filter(listing => listing.type === priceType); if (relevantListings.length === 0) { throw new Error(`No ${priceType} listings found`); } if (priceType === 'sell') { resolve(Math.min(...relevantListings.map(listing => listing.price))); } else { resolve(Math.max(...relevantListings.map(listing => listing.price))); } } catch (error) { reject(error); } }, onerror: function(error) { reject(error); } }); }); } function calculateTierMultiplier(tier, isGold, isScarletMerchant) { if (isGold) { if (isScarletMerchant) { return 1 + (tier * 0.2); } else { return 1 + (tier * 0.15); } } else { return 1 + (tier * 0.75); } } async function calculateGpA() { // Use the specific class to get monster rows const rows = document.querySelectorAll('.monster-row'); let maxGpA = -1; let maxGpARow = null; // Reset previous results document.querySelectorAll('.gpa-result').forEach(result => { result.textContent = ''; result.style.color = ''; }); // First refresh all market prices for (const row of rows) { const monsterName = row.querySelector('div').textContent; const monsterInfo = monsterData[monsterName]; const selectedPriceType = row.querySelector('input[type="radio"]:checked')?.value; const priceInput = row.querySelector('.price-input'); if (monsterInfo.itemId && selectedPriceType !== 'manual') { try { const price = await fetchMarketData(monsterInfo.itemId, selectedPriceType === 'buy price' ? 'buy' : 'sell'); priceInput.value = price; } catch (error) { console.error(`Error refreshing price for ${monsterName}:`, error); } } } for (const row of rows) { const monsterName = row.querySelector('div').textContent; const monsterInfo = monsterData[monsterName]; const tier = parseFloat(row.querySelector('.tier-input').value) || 0; const winRate = parseFloat(row.querySelector('.winrate-input').value) || 0; // Get individual values first… let midasPercentage = parseFloat(row.querySelector('.midas-input').value) || 0; let extractorPercentage = parseFloat(row.querySelector('.extractor-input').value) || 0; // Override with global values if uniform mode is enabled const uniformMidas = document.getElementById('uniformMidasCheckbox').checked; const uniformExtractor = document.getElementById('uniformExtractorCheckbox').checked; if (uniformMidas) { midasPercentage = parseFloat(document.getElementById('globalMidasInput').value) || 0; } if (uniformExtractor) { extractorPercentage = parseFloat(document.getElementById('globalExtractorInput').value) || 0; } let mobDropPrice = 0; const selectedPriceType = row.querySelector('input[type="radio"]:checked')?.value; if (monsterInfo.itemId && selectedPriceType !== 'manual') { try { mobDropPrice = await fetchMarketData(monsterInfo.itemId, selectedPriceType === 'buy price' ? 'buy' : 'sell'); } catch (error) { console.error(`Error fetching price for ${monsterName}:`, error); } } else if (selectedPriceType === 'manual') { mobDropPrice = parseFloat(row.querySelector('.price-input').value) || 0; } const mobDropMultiplier = calculateTierMultiplier(tier, false, false); const goldMultiplier = calculateTierMultiplier(tier, true, monsterName === 'Scarlet Merchant'); const gpa = ( ((winRate / 100) * monsterInfo.baseMobDropChance * mobDropMultiplier / 100) * (1 + extractorPercentage / 100) * mobDropPrice + ((winRate / 100) * monsterInfo.baseGoldDropChance * goldMultiplier / 100) * (1 + midasPercentage / 100) ); const gpaResult = row.querySelector('.gpa-result'); gpaResult.textContent = gpa.toFixed(2); if (gpa > maxGpA) { maxGpA = gpa; maxGpARow = row; } } if (maxGpARow) { const checkmark = '✓'; maxGpARow.querySelector('.gpa-result').style.color = '#4CAF50'; maxGpARow.querySelector('.gpa-result').textContent += ` ${checkmark}`; } } function toggleCalculator() { const calculator = document.getElementById('combatCalculator'); const overlay = document.getElementById('calculatorOverlay'); const isVisible = calculator.style.display === 'block'; calculator.style.display = isVisible ? 'none' : 'block'; overlay.style.display = isVisible ? 'none' : 'block'; } // Initialize the calculator createButton(); createCalculatorUI(); })();