您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Provides some general additions to Elethor
当前为
// ==UserScript== // @name Elethor General Purpose // @description Provides some general additions to Elethor // @namespace https://www.elethor.com/ // @version 1.7.15 // @author Xortrox // @contributor Kidel // @contributor Saya // @contributor Archeron // @contributor Hito // @match https://elethor.com/* // @match https://www.elethor.com/* // @run-at document-start // @grant none // ==/UserScript== (function() { const currentUserData = {}; const moduleName = 'Elethor General Purpose'; const version = '1.7.9'; const profileURL = '/profile/'; function initializeXHRHook() { let rawSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { if (!this._hooked) { this._hooked = true; this.addEventListener('readystatechange', function() { if (this.readyState === XMLHttpRequest.DONE) { setupHook(this); } }, false); } rawSend.apply(this, arguments); } function setupHook(xhr) { if (window.elethorGeneralPurposeOnXHR) { const e = new Event('EGPXHR'); e.xhr = xhr; window.elethorGeneralPurposeOnXHR.dispatchEvent(e); } } window.elethorGeneralPurposeOnXHR = new EventTarget(); console.log(`[${moduleName} v${version}] XHR Hook initialized.`); } function initializeUserLoadListener() { elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) { console.log('user load?:', e?.xhr?.responseURL); if (e && e.xhr && e.xhr.responseURL && e.xhr.responseURL.endsWith && e.xhr.responseURL.endsWith('/game/user') ) { try { const userData = JSON.parse(e.xhr.responseText); if (userData) { for (const key of Object.keys(userData)) { currentUserData[key] = userData[key]; } } } catch (e) { console.log(`[${moduleName} v${version}] Error parsing userData:`, e); } } }); console.log(`[${moduleName} v${version}] User Load Listener initialized.`); } function initializeToastKiller() { document.addEventListener('click', function(e) { if (e.target && e.target.className && e.target.className.includes && e.target.className.includes('toasted toasted-primary') ) { e.target.remove(); } }); console.log(`[${moduleName} v${version}] Toast Killer initialized.`); } async function forceLoadUser() { const userLoad = await getUserSelf(); } function initializeInventoryStatsLoadListener() { elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) { if (e && e.xhr && e.xhr.responseURL && e.xhr.responseURL.endsWith && e.xhr.responseURL.endsWith('/game/inventory/stats') ) { setTimeout(async () => { if (Object.keys(currentUserData).length === 0) { await forceLoadUser(); } updateEquipmentPercentageSummary(); setTimeout(updateInventoryStatsPercentages, 1000); }); } }); console.log(`[${moduleName} v${version}] Inventory Stats Load Listener initialized.`); } function updateEquipmentPercentageSummary() { document.querySelector('.contains-equipment>div>div:nth-child(1)').setAttribute('style', 'width: 50%'); document.querySelector('.contains-equipment>div>div:nth-child(2)').setAttribute('style', 'width: 25%'); let percentagesTable = document.querySelector('#egpPercentagesSummary') if (!percentagesTable){ percentagesTable = document.querySelector('.contains-equipment>div>div:nth-child(2)').cloneNode(true); percentagesTable.setAttribute('style', 'width: 25%'); percentagesTable.id='egpPercentagesSummary'; document.querySelector('.contains-equipment>div').appendChild(percentagesTable); for (const child of percentagesTable.children[0].children) { if (child && child.children && child.children[0]) { child.children[0].remove(); } } document.querySelector('#egpPercentagesSummary>table>tr:nth-child(8)').setAttribute('style', 'height:43px'); } } function getStatSummary(equipment) { const summary = { base: {}, energizements: {} }; if (equipment) { for (const key of Object.keys(equipment)) { const item = equipment[key]; /** * Sums base attributes by name * */ if (item && item.attributes) { for (const attributeName of Object.keys(item.attributes)) { const attributeValue = item.attributes[attributeName]; if (!summary.base[attributeName]) { summary.base[attributeName] = 0; } summary.base[attributeName] += attributeValue; } } /** * Sums energizements by stat name * */ if (item && item.upgrade && item.upgrade.energizements) { for (const energizement of item.upgrade.energizements) { if (!summary.energizements[energizement.stat]) { summary.energizements[energizement.stat] = 0; } summary.energizements[energizement.stat] += Number(energizement.boost); } } } } return summary; } function updateInventoryStatsPercentages() { let percentagesTable = document.querySelector('#egpPercentagesSummary') if (percentagesTable && currentUserData && currentUserData.equipment){ const statSummary = getStatSummary(currentUserData.equipment); const baseKeys = Object.keys(statSummary.base); const energizementKeys = Object.keys(statSummary.energizements); let allKeys = baseKeys.concat(energizementKeys); const filterUniques = {}; for (const key of allKeys){ filterUniques[key] = true; } allKeys = Object.keys(filterUniques); allKeys.sort(); allKeys.push('actions'); const tableRows = percentagesTable.children[0].children; for(const row of tableRows) { if (row && row.children && row.children[0] && row.children[0].children[0] ) { const rowText = row.children[0].children[0]; rowText.innerText = ''; } } let rowIndex = 0; for (const key of allKeys) { if (key === 'puncture') { continue; } const row = tableRows[rowIndex]; if (row && row.children && row.children[0] && row.children[0].children[0] ) { const rowText = row.children[0].children[0]; const rowBase = statSummary.base[key] || 0; const rowEnergizement = (statSummary.energizements[key] || 0); const rowEnergizementPercentage = (statSummary.energizements[key] || 0) * 100; if (key.startsWith('+')) { rowText.innerText = `${key} per 10 levels: ${rowEnergizement}`; } else if (key === 'actions') { const actions = currentUserData.user.bonus_actions || 0; rowText.innerText = `Bonus Actions: ${actions}`; } else { rowText.innerText = `${key}: ${rowBase} (${rowEnergizementPercentage.toFixed(0)}%)`; } rowIndex++; } } } } function initializeLocationChangeListener() { let previousLocation = window.location.href; window.elethorGeneralPurposeOnLocationChange = new EventTarget(); window.elethorLocationInterval = setInterval(() => { if (previousLocation !== window.location.href) { previousLocation = window.location.href; const e = new Event('EGPLocation'); e.newLocation = window.location.href; window.elethorGeneralPurposeOnLocationChange.dispatchEvent(e); } }, 500); console.log(`[${moduleName} v${version}] Location Change Listener initialized.`); } function getProfileCombatElement() { const skillElements = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:first-child'); const skillElements2 = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:nth-child(2)'); let index = 0; for (const skillElement of skillElements2) { if (skillElement.innerText?.toLowerCase().includes('combat')) { return skillElements[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement; } index++; } } function getProfileMiningElement() { const skillElements = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:first-child'); const skillElements2 = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:nth-child(2)'); let index = 0; for (const skillElement of skillElements2) { if (skillElement.innerText?.toLowerCase().includes('mining')) { return skillElements[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement; } index++; } } function updateXPTracker(difference) { const combatElement = getProfileCombatElement(); if (combatElement) { if (difference.combat > 0) { combatElement.setAttribute('data-combat-experience-ahead', `(+${formatNormalNumber(difference.combat)})`); combatElement.setAttribute('style', `color:lime`); } else { combatElement.setAttribute('data-combat-experience-ahead', `(${formatNormalNumber(difference.combat)})`); combatElement.setAttribute('style', `color:red`); } } const miningElement = getProfileMiningElement(); if (difference.mining > 0) { miningElement.setAttribute('data-mining-experience-ahead', `(+${formatNormalNumber(difference.mining)})`); miningElement.setAttribute('style', `color:lime`); } else { miningElement.setAttribute('data-mining-experience-ahead', `(${formatNormalNumber(difference.mining)})`); miningElement.setAttribute('style', `color:red`); } } function initializeProfileLoadListener() { let css = '[data-combat-experience-ahead]::after { content: attr(data-combat-experience-ahead); padding: 12px;}'; css += '[data-mining-experience-ahead]::after { content: attr(data-mining-experience-ahead); padding: 12px;}'; appendCSS(css); window.elethorGeneralPurposeOnLocationChange.addEventListener('EGPLocation', async function (e) { if (e && e.newLocation) { if(e.newLocation.includes('/profile/')) { console.log('Profile view detected:', e); const url = e.newLocation; const path = url.substr(url.indexOf(profileURL)); // We know we have a profile lookup, and not user-data load if the length differs. if (path.length > profileURL.length) { const userId = Number(path.substr(path.lastIndexOf('/') + 1)); const difference = await getExperienceDifference(userId, currentUserData.user.id); updateXPTracker(difference); } } } }); console.log(`[${moduleName} v${version}] Profile Load Listener initialized.`); } async function getUser(id) { const result = await window.axios.get(`/game/user/${id}?egpIgnoreMe=true`); return result.data; } window.getUser = getUser; async function getUserSelf() { const result = await window.axios.get(`/game/user`); return result.data; } window.getUserSelf = getUserSelf; async function getUserStats() { const result = await window.axios.get(`/game/inventory/stats`); return result.data; } window.getUserStats = getUserStats; async function getUserStatsJSON(pretty) { const stats = await getUserStats(); if (pretty) { return JSON.stringify(stats, null, 2); } return JSON.stringify(stats); } window.getUserStatsJSON = getUserStatsJSON; function getUserCombatStats(user) { for (const skill of user.skills) { if (skill.name === 'combat') { return skill.pivot; } } } function getMiningStats(user) { for (const skill of user.skills) { if (skill.id === 1) { return skill.pivot; } } return 0; } async function getExperienceDifference(userId1, userId2) { const [user1, user2] = await Promise.all([ getUser(userId1), getUser(userId2) ]); const combatStats1 = getUserCombatStats(user1); const miningStats1 = getMiningStats(user1); const combatStats2 = getUserCombatStats(user2); const miningStats2 = getMiningStats(user2); return { combat: combatStats2.experience - combatStats1.experience, mining: miningStats2.experience - miningStats1.experience, }; } (async function run() { await waitForField(window, 'axios'); forceLoadUser(); initializeToastKiller(); initializeXHRHook(); initializeUserLoadListener(); initializeInventoryStatsLoadListener(); initializeLocationChangeListener(); initializeProfileLoadListener(); loadMarketRecyclobotVisualizer(); console.log(`[${moduleName} v${version}] Loaded.`); })(); (async function loadRerollDisableButtonModule() { async function waitForEcho() { return new Promise((resolve, reject) => { const interval = setInterval(() => { if (window.Echo) { clearInterval(interval); resolve(); } }, 100); }); } await waitForEcho(); await waitForUser(); elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', async function (e) { if (e && e.xhr && e.xhr.responseURL) { if(e.xhr.responseURL.includes('/game/energize')) { const itemID = e.xhr.responseURL.substr(e.xhr.responseURL.lastIndexOf('/')+1); window.lastEnergizeID = Number(itemID); } } }); })(); (async function loadResourceNodeUpdater() { await waitForField(currentUserData, 'user'); const user = await getUser(currentUserData.user.id); function updateExperienceRates() { document.querySelectorAll('#nodeContainer>div:not(:first-child)').forEach(async (node) => { visualizeResourceNodeExperienceRates(node, user) }); function visualizeResourceNodeExperienceRates(node, user) { const purity = getNodePurityPercentage(node, user); const density = getNodeDensityPercentage(node, user); const experience = getNodeExperience(node, density, user); const ore = 16; const experienceRate = experience.toFixed(2); const oreRate = getOreRate(density, purity, ore); node.children[0].children[0].setAttribute('data-after', `${experienceRate} xp/h ${oreRate} ore/h`); } function getNodePurityPercentage(node, user) { const column = node.children[0].children[0].children[2]; if (!column || !column.querySelectorAll('.font-bold')[0]) return; const label = column.querySelectorAll('.font-bold')[0].parentElement; let percentage = Number(label.innerText.replace('%','').split(':')[1]); let miningLevel = getMiningLevel(user); percentage = percentage + (miningLevel * 0.1); return percentage; } function getNodeDensityPercentage(node, user) { const column = node.children[0].children[0].children[1]; if (!column || !column.querySelectorAll('.font-bold')[0]) return; const label = column.querySelectorAll('.font-bold')[0].parentElement; let percentage = Number(label.innerText.replace('%','').split(':')[1]); let miningLevel = getMiningLevel(user); percentage = percentage + (miningLevel * 0.1); return percentage; } function getNodeExperience(node, density, user) { /** Gets the Exp: label */ const column = node.children[0].children[0].children[3]; const label = column.querySelectorAll('.font-bold')[0].parentElement; let value = Number(label.innerText.replace('%','').split(':')[1]); const skilledExtraction = getSkilledExtractionLevel(user); const knowledgeExtraction = getKnowledgeExtractionLevel(user) / 100; const ore = getActiveMining(); const expertiseLevel = getExpertiseLevel(user); const expertiseGain = getExpertiseXPGainByOre(ore); const additionalMiningGain = expertiseLevel / 5 * expertiseGain; console.log('additionalMiningGain:', additionalMiningGain); value += additionalMiningGain; const actionsPerHour = getActionsPerHour(density); const experienceBase = value * actionsPerHour; const experienceSkilled = actionsPerHour * skilledExtraction; const experienceKnowledge = value * knowledgeExtraction; value = experienceBase + experienceSkilled + experienceKnowledge; const vip = isUserVIP(user); value *= vip ? 1.1 : 1; return value; } function getActionsPerHour(density) { return 3600 / (60 / (density / 100)) } function getExperienceRate(density, experience) { return Number((3600 / (60 / (density / 100)) * experience).toFixed(2)); } function getOreRate(density, purity, ore) { return Number((3600 / (60 / (density / 100)) * (purity / 100) * ore).toFixed(2)); } } function isUserVIP(user) { return new Date(user.vip_expires) > new Date(); } updateExperienceRates(); window.elethorResourceInterval = setInterval(updateExperienceRates, 500); initializeResourceNodeView(); async function initializeResourceNodeView() { await waitForField(document, 'head'); let css = '[data-after]::after { content: attr(data-after); padding: 12px;}'; appendCSS(css); } })(); async function loadMarketRecyclobotVisualizer() { if (!window.egpItemPoints) { const companions = await getCompanions(); const recyclobot = companions.recyclobot; window.egpItemPoints = recyclobot.itemPoints; console.log(`[${moduleName} v${version}] Companion Data initialized.`); } window.elethorGeneralPurposeOnLocationChange.addEventListener('EGPLocation', async function (e) { if (e && e.newLocation) { window.egpRecyclobotOnMarket = e.newLocation.includes('/market/'); if (!window.egpRecyclobotOnMarket) { return; } if (!window.egpItemPoints) { console.warn(`[${moduleName} v${version}] Entered market page but had no recyclobot item points list.`); return; } const marketObjects = getMarketListingObjects(); for (const marketListing of marketObjects) { for (const recyclobotEntry of window.egpItemPoints) { if (recyclobotEntry.item_name === marketListing.name) { //marketListing.element.setAttribute('data-points-recyclobot',`R: ${recyclobotEntry.quantity}:${recyclobotEntry.points}`); break; } } } } }); function getMarketListingObjects() { return Array.prototype.slice.call(document.querySelectorAll('.is-market-side-menu ul li')).map((e) => {return {element: e, name: e.innerText}}); } async function getCompanions() { return (await window.axios.get('/game/companions')).data; } }; function setPackAmount(amount) { window.packAmount = amount; if (window.packAmount < 0) { window.packAmount = 0; } } window.setPackAmount = setPackAmount; function formatNormalNumber(num){ return num.toLocaleString(); } function appendCSS(css) { let head = document.head || document.getElementsByTagName('head')[0]; let style = document.createElement('style'); head.appendChild(style); style.type = 'text/css'; if (style.styleSheet){ // This is required for IE8 and below. style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } function getMiningLevel(user) { for (const skill of user.skills) { if (skill.id === 1) { return skill.pivot.level; } } return 0; } function getSkilledExtractionLevel(user) { for (const skill of user.skills) { if (skill.id === 17) { return skill.pivot.level; } } return 0; } function getKnowledgeExtractionLevel(user) { for (const skill of user.skills) { if (skill.id === 18) { return skill.pivot.level; } } return 0; } function getExpertiseLevel(user) { for (const skill of user.skills) { if (skill.id === 38) { return skill.pivot.level; } } return 0; } expertiseXPGainByOre = { 'Orthoclase': 780, 'Anorthite': 825, 'Ferrisum': 600, 'Rhenium': 465, 'Jaspil': 480, 'Skasix': 0, }; function getExpertiseXPGainByOre(ore) { return expertiseXPGainByOre[ore]; } function getActiveMining() { const activeMiningButtons = document.querySelectorAll('.buttons .button.is-success'); if (!activeMiningButtons[0] || !activeMiningButtons[0].children[0] || !activeMiningButtons[0].children[0].innerText || !activeMiningButtons[0].children[0].innerText.trim ) { return ''; } return activeMiningButtons[0].children[0].innerText.trim(); } function getPackLevel(user) { for (const skill of user.skills) { if (skill.id === 22) { return skill.pivot.level; } } return 0; } function getCarnageLevel(user) { for (const skill of user.skills) { if (skill.id === 23) { return skill.pivot.level; } } return 0; } function getAttackSpeed(speed) { return 50 - (50 * speed / (speed + 400)); } function getHealth(fortitude) { return 100000 * fortitude / (fortitude + 4000); } async function waitForField(target, field) { return new Promise((resolve, reject) => { const interval = setInterval(() => { if (target[field] !== undefined) { clearInterval(interval); resolve(); } }, 100); }); } async function waitForUser() { return new Promise((resolve, reject) => { const interval = setInterval(() => { if (currentUserData.user && currentUserData.user.id !== undefined) { clearInterval(interval); resolve(); } }, 100); }); } (async function loadAlarms() { await waitForField(window, 'Echo'); await waitForField(document, 'head'); await waitForUser(); const meta1 = document.createElement('meta'); meta1.setAttribute('content', "media-src https://furious.no/elethor/sound/"); meta1.setAttribute('http-equiv', 'Content-Security-Policy'); document.getElementsByTagName('head')[0].appendChild(meta1); /** * You can add an alternative audio by setting the local storage keys: * localStorage.setItem('egpSoundOutOfActions', 'your-url') * localStorage.setItem('egpSoundQuestCompleted', 'your-url') * */ // Alternative: https://furious.no/elethor/sound/elethor-actions-have-ran-out.mp3 window.egpSoundOutOfActions = localStorage.getItem('egpSoundOutOfActions') || 'https://furious.no/elethor/sound/out-of-actions.wav'; // Alternative: https://furious.no/elethor/sound/elethor-quest-completed.mp3 window.egpSoundQuestCompleted = localStorage.getItem('egpSoundQuestCompleted') || 'https://furious.no/elethor/sound/quest-complete.wav'; const masterAudioLevel = 0.3; window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\Monster\\Events\\FightingAgain", handleActionData); window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\ResourceNode\\Events\\GatheringAgain", handleActionData); window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\Quest\\Events\\UpdateQuestProgress", handleQuestData); let hasWarnedAboutActions = false; let hasWarnedAboutQuest = false; /** * Warns once when action runs out. * */ function handleActionData(data) { if (data && data.action) { if (data.action.remaining <= 0 && !hasWarnedAboutActions) { playOutOfActions(); hasWarnedAboutActions = true; } else if (data.action.remaining > 0 && hasWarnedAboutActions) { hasWarnedAboutActions = false; } } } window.handleActionData = handleActionData; /** * Warns once when quest completes. * */ function handleQuestData(data) { if (data && data.step && data.step.tasks) { if (data.step.progress >= data.step.tasks[0].quantity && !hasWarnedAboutQuest) { playQuestCompleted(); hasWarnedAboutQuest = true; } else if (data.step.progress < data.step.tasks[0].quantity && hasWarnedAboutQuest) { hasWarnedAboutQuest = false; } } } window.handleQuestData = handleQuestData; function playOutOfActions() { playSound(window.egpSoundOutOfActions); } window.playOutOfActions = playOutOfActions; function playQuestCompleted() { playSound(window.egpSoundQuestCompleted); } window.playQuestCompleted = playQuestCompleted; function playSound(sound, volume = masterAudioLevel){ const audio = new Audio(sound); audio.volume = volume; audio.play(); } })(); })();