Elethor General Purpose

Provides some general additions to Elethor

目前为 2023-08-11 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Elethor General Purpose
  3. // @description Provides some general additions to Elethor
  4. // @namespace https://www.elethor.com/
  5. // @version 1.7.17
  6. // @author Xortrox
  7. // @contributor Kidel
  8. // @contributor Saya
  9. // @contributor Archeron
  10. // @contributor Hito
  11. // @match https://elethor.com/*
  12. // @match https://www.elethor.com/*
  13. // @run-at document-start
  14. // @grant none
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. const currentUserData = {};
  19.  
  20. const moduleName = 'Elethor General Purpose';
  21. const version = '1.7.9';
  22.  
  23. const profileURL = '/profile/';
  24.  
  25. function initializeXHRHook() {
  26. let rawSend = XMLHttpRequest.prototype.send;
  27.  
  28. XMLHttpRequest.prototype.send = function() {
  29. if (!this._hooked) {
  30. this._hooked = true;
  31.  
  32. this.addEventListener('readystatechange', function() {
  33. if (this.readyState === XMLHttpRequest.DONE) {
  34. setupHook(this);
  35. }
  36. }, false);
  37. }
  38. rawSend.apply(this, arguments);
  39. }
  40.  
  41. function setupHook(xhr) {
  42. if (window.elethorGeneralPurposeOnXHR) {
  43. const e = new Event('EGPXHR');
  44. e.xhr = xhr;
  45.  
  46. window.elethorGeneralPurposeOnXHR.dispatchEvent(e);
  47. }
  48. }
  49. window.elethorGeneralPurposeOnXHR = new EventTarget();
  50.  
  51. console.log(`[${moduleName} v${version}] XHR Hook initialized.`);
  52. }
  53.  
  54. function initializeUserLoadListener() {
  55. elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) {
  56. console.log('user load?:', e?.xhr?.responseURL);
  57. if (e && e.xhr
  58. && e.xhr.responseURL
  59. && e.xhr.responseURL.endsWith
  60. && e.xhr.responseURL.endsWith('/game/user')
  61. ) {
  62. try {
  63. const userData = JSON.parse(e.xhr.responseText);
  64.  
  65. if (userData) {
  66. for (const key of Object.keys(userData)) {
  67. currentUserData[key] = userData[key];
  68. }
  69. }
  70. } catch (e) {
  71. console.log(`[${moduleName} v${version}] Error parsing userData:`, e);
  72. }
  73.  
  74. }
  75. });
  76.  
  77. console.log(`[${moduleName} v${version}] User Load Listener initialized.`);
  78. }
  79.  
  80. function initializeToastKiller() {
  81. document.addEventListener('click', function(e) {
  82. if (e.target
  83. && e.target.className
  84. && e.target.className.includes
  85. && e.target.className.includes('toasted toasted-primary')
  86. ) {
  87. e.target.remove();
  88. }
  89. });
  90.  
  91. console.log(`[${moduleName} v${version}] Toast Killer initialized.`);
  92. }
  93.  
  94. async function forceLoadUser() {
  95. const userLoad = await getUserSelf();
  96. }
  97.  
  98. function initializeInventoryStatsLoadListener() {
  99. elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', function (e) {
  100. if (e && e.xhr
  101. && e.xhr.responseURL
  102. && e.xhr.responseURL.endsWith
  103. && e.xhr.responseURL.endsWith('/game/inventory/stats')
  104. ) {
  105. setTimeout(async () => {
  106. if (Object.keys(currentUserData).length === 0) {
  107. await forceLoadUser();
  108. }
  109.  
  110. updateEquipmentPercentageSummary();
  111.  
  112. setTimeout(updateInventoryStatsPercentages, 1000);
  113. });
  114. }
  115. });
  116.  
  117. console.log(`[${moduleName} v${version}] Inventory Stats Load Listener initialized.`);
  118. }
  119.  
  120. function updateEquipmentPercentageSummary() {
  121. document.querySelector('.contains-equipment>div>div:nth-child(1)').setAttribute('style', 'width: 50%');
  122. document.querySelector('.contains-equipment>div>div:nth-child(2)').setAttribute('style', 'width: 25%');
  123. let percentagesTable = document.querySelector('#egpPercentagesSummary')
  124. if (!percentagesTable){
  125. percentagesTable = document.querySelector('.contains-equipment>div>div:nth-child(2)').cloneNode(true);
  126. percentagesTable.setAttribute('style', 'width: 25%');
  127. percentagesTable.id='egpPercentagesSummary';
  128. document.querySelector('.contains-equipment>div').appendChild(percentagesTable);
  129.  
  130. for (const child of percentagesTable.children[0].children) {
  131. if (child && child.children && child.children[0]) {
  132. child.children[0].remove();
  133. }
  134. }
  135.  
  136. document.querySelector('#egpPercentagesSummary>table>tr:nth-child(8)').setAttribute('style', 'height:43px');
  137. }
  138. }
  139.  
  140. function getStatSummary(equipment) {
  141. const summary = {
  142. base: {},
  143. energizements: {}
  144. };
  145.  
  146. if (equipment) {
  147. for (const key of Object.keys(equipment)) {
  148. const item = equipment[key];
  149.  
  150. /**
  151. * Sums base attributes by name
  152. * */
  153. if (item && item.attributes) {
  154. for (const attributeName of Object.keys(item.attributes)) {
  155. const attributeValue = item.attributes[attributeName];
  156.  
  157. if (!summary.base[attributeName]) {
  158. summary.base[attributeName] = 0;
  159. }
  160.  
  161. summary.base[attributeName] += attributeValue;
  162. }
  163. }
  164.  
  165. /**
  166. * Sums energizements by stat name
  167. * */
  168. if (item && item.upgrade && item.upgrade.energizements) {
  169. for (const energizement of item.upgrade.energizements) {
  170. if (!summary.energizements[energizement.stat]) {
  171. summary.energizements[energizement.stat] = 0;
  172. }
  173.  
  174. summary.energizements[energizement.stat] += Number(energizement.boost);
  175. }
  176. }
  177. }
  178. }
  179.  
  180. return summary;
  181. }
  182.  
  183. function updateInventoryStatsPercentages() {
  184. let percentagesTable = document.querySelector('#egpPercentagesSummary')
  185. if (percentagesTable && currentUserData && currentUserData.equipment){
  186. const statSummary = getStatSummary(currentUserData.equipment);
  187.  
  188. const baseKeys = Object.keys(statSummary.base);
  189. const energizementKeys = Object.keys(statSummary.energizements);
  190.  
  191. let allKeys = baseKeys.concat(energizementKeys);
  192. const filterUniques = {};
  193. for (const key of allKeys){
  194. filterUniques[key] = true;
  195. }
  196. allKeys = Object.keys(filterUniques);
  197. allKeys.sort();
  198.  
  199. allKeys.push('actions');
  200.  
  201. const tableRows = percentagesTable.children[0].children;
  202.  
  203. for(const row of tableRows) {
  204. if (row
  205. && row.children
  206. && row.children[0]
  207. && row.children[0].children[0]
  208. ) {
  209. const rowText = row.children[0].children[0];
  210. rowText.innerText = '';
  211. }
  212. }
  213.  
  214. let rowIndex = 0;
  215. for (const key of allKeys) {
  216. if (key === 'puncture') {
  217. continue;
  218. }
  219.  
  220. const row = tableRows[rowIndex];
  221. if (row
  222. && row.children
  223. && row.children[0]
  224. && row.children[0].children[0]
  225. ) {
  226. const rowText = row.children[0].children[0];
  227.  
  228. const rowBase = statSummary.base[key] || 0;
  229. const rowEnergizement = (statSummary.energizements[key] || 0);
  230. const rowEnergizementPercentage = (statSummary.energizements[key] || 0) * 100;
  231.  
  232. if (key.startsWith('+')) {
  233. rowText.innerText = `${key} per 10 levels: ${rowEnergizement}`;
  234. } else if (key === 'actions') {
  235. const actions = currentUserData.user.bonus_actions || 0;
  236. rowText.innerText = `Bonus Actions: ${actions}`;
  237. } else {
  238. rowText.innerText = `${key}: ${rowBase} (${rowEnergizementPercentage.toFixed(0)}%)`;
  239. }
  240.  
  241. rowIndex++;
  242. }
  243. }
  244. }
  245. }
  246.  
  247. function initializeLocationChangeListener() {
  248. let previousLocation = window.location.href;
  249.  
  250. window.elethorGeneralPurposeOnLocationChange = new EventTarget();
  251.  
  252. window.elethorLocationInterval = setInterval(() => {
  253. if (previousLocation !== window.location.href) {
  254. previousLocation = window.location.href;
  255.  
  256. const e = new Event('EGPLocation');
  257. e.newLocation = window.location.href;
  258. window.elethorGeneralPurposeOnLocationChange.dispatchEvent(e);
  259. }
  260.  
  261. }, 500);
  262.  
  263. console.log(`[${moduleName} v${version}] Location Change Listener initialized.`);
  264. }
  265.  
  266. function getProfileCombatElement() {
  267. const skillElements = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:first-child');
  268. const skillElements2 = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:nth-child(2)');
  269.  
  270. let index = 0;
  271. for (const skillElement of skillElements2) {
  272. if (skillElement.innerText?.toLowerCase().includes('combat')) {
  273. return skillElements[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement;
  274. }
  275. index++;
  276. }
  277. }
  278.  
  279. function getProfileMiningElement() {
  280. const skillElements = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:first-child');
  281. const skillElements2 = document.querySelectorAll('.is-round-skill .progressbar-text>div>p:nth-child(2)');
  282.  
  283. let index = 0;
  284. for (const skillElement of skillElements2) {
  285. if (skillElement.innerText?.toLowerCase().includes('mining')) {
  286. return skillElements[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement;
  287. }
  288. index++;
  289. }
  290. }
  291.  
  292. function updateXPTracker(difference) {
  293. const combatElement = getProfileCombatElement();
  294. if (combatElement) {
  295. if (difference.combat > 0) {
  296. combatElement.setAttribute('data-combat-experience-ahead', `(+${formatNormalNumber(difference.combat)})`);
  297. combatElement.setAttribute('style', `color:lime`);
  298. } else {
  299. combatElement.setAttribute('data-combat-experience-ahead', `(${formatNormalNumber(difference.combat)})`);
  300. combatElement.setAttribute('style', `color:red`);
  301. }
  302. }
  303.  
  304. const miningElement = getProfileMiningElement();
  305. if (difference.mining > 0) {
  306. miningElement.setAttribute('data-mining-experience-ahead', `(+${formatNormalNumber(difference.mining)})`);
  307. miningElement.setAttribute('style', `color:lime`);
  308. } else {
  309. miningElement.setAttribute('data-mining-experience-ahead', `(${formatNormalNumber(difference.mining)})`);
  310. miningElement.setAttribute('style', `color:red`);
  311. }
  312. }
  313.  
  314. function initializeProfileLoadListener() {
  315. let css = '[data-combat-experience-ahead]::after { content: attr(data-combat-experience-ahead); padding: 12px;}';
  316. css += '[data-mining-experience-ahead]::after { content: attr(data-mining-experience-ahead); padding: 12px;}';
  317.  
  318. appendCSS(css);
  319.  
  320. window.elethorGeneralPurposeOnLocationChange.addEventListener('EGPLocation', async function (e) {
  321. if (e && e.newLocation) {
  322. if(e.newLocation.includes('/profile/')) {
  323. console.log('Profile view detected:', e);
  324. const url = e.newLocation;
  325. const path = url.substr(url.indexOf(profileURL));
  326.  
  327. // We know we have a profile lookup, and not user-data load if the length differs.
  328. if (path.length > profileURL.length) {
  329. const userId = Number(path.substr(path.lastIndexOf('/') + 1));
  330.  
  331. const difference = await getExperienceDifference(userId, currentUserData.user.id);
  332.  
  333. updateXPTracker(difference);
  334. }
  335. }
  336. }
  337. });
  338.  
  339. console.log(`[${moduleName} v${version}] Profile Load Listener initialized.`);
  340. }
  341.  
  342. async function getUser(id) {
  343. const result = await window.axios.get(`/game/user/${id}?egpIgnoreMe=true`);
  344. return result.data;
  345. }
  346.  
  347. window.getUser = getUser;
  348.  
  349. async function getUserSelf() {
  350. const result = await window.axios.get(`/game/user`);
  351. return result.data;
  352. }
  353.  
  354. window.getUserSelf = getUserSelf;
  355.  
  356. async function getUserStats() {
  357. const result = await window.axios.get(`/game/inventory/stats`);
  358. return result.data;
  359. }
  360.  
  361. window.getUserStats = getUserStats;
  362.  
  363. async function getUserStatsJSON(pretty) {
  364. const stats = await getUserStats();
  365.  
  366. if (pretty) {
  367. return JSON.stringify(stats, null, 2);
  368. }
  369.  
  370. return JSON.stringify(stats);
  371. }
  372.  
  373. window.getUserStatsJSON = getUserStatsJSON;
  374.  
  375. function getUserCombatStats(user) {
  376. for (const skill of user.skills) {
  377. if (skill.name === 'combat') {
  378. return skill.pivot;
  379. }
  380. }
  381. }
  382.  
  383. function getMiningStats(user) {
  384. for (const skill of user.skills) {
  385. if (skill.id === 1) {
  386. return skill.pivot;
  387. }
  388. }
  389.  
  390. return 0;
  391. }
  392.  
  393. async function getExperienceDifference(userId1, userId2) {
  394. const [user1, user2] = await Promise.all([
  395. getUser(userId1),
  396. getUser(userId2)
  397. ]);
  398.  
  399. const combatStats1 = getUserCombatStats(user1);
  400. const miningStats1 = getMiningStats(user1);
  401.  
  402. const combatStats2 = getUserCombatStats(user2);
  403. const miningStats2 = getMiningStats(user2);
  404.  
  405. return {
  406. combat: combatStats2.experience - combatStats1.experience,
  407. mining: miningStats2.experience - miningStats1.experience,
  408. };
  409. }
  410.  
  411. (async function run() {
  412. await waitForField(window, 'axios');
  413. forceLoadUser();
  414. initializeToastKiller();
  415. initializeXHRHook();
  416. initializeUserLoadListener();
  417. initializeInventoryStatsLoadListener();
  418. initializeLocationChangeListener();
  419. initializeProfileLoadListener();
  420. loadMarketRecyclobotVisualizer();
  421.  
  422. console.log(`[${moduleName} v${version}] Loaded.`);
  423. })();
  424.  
  425. (async function loadRerollDisableButtonModule() {
  426. async function waitForEcho() {
  427. return new Promise((resolve, reject) => {
  428. const interval = setInterval(() => {
  429. if (window.Echo) {
  430. clearInterval(interval);
  431. resolve();
  432. }
  433. }, 100);
  434. });
  435. }
  436.  
  437. await waitForEcho();
  438. await waitForUser();
  439.  
  440. elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', async function (e) {
  441. if (e && e.xhr && e.xhr.responseURL) {
  442. if(e.xhr.responseURL.includes('/game/energize')) {
  443. const itemID = e.xhr.responseURL.substr(e.xhr.responseURL.lastIndexOf('/')+1);
  444. window.lastEnergizeID = Number(itemID);
  445. }
  446. }
  447. });
  448. })();
  449.  
  450. (async function loadResourceNodeUpdater() {
  451. await waitForField(currentUserData, 'user');
  452. const user = await getUser(currentUserData.user.id);
  453.  
  454. function updateExperienceRates() {
  455. document.querySelectorAll('#nodeContainer>div:not(:first-child)').forEach(async (node) => {
  456. visualizeResourceNodeExperienceRates(node, user)
  457. });
  458.  
  459. function visualizeResourceNodeExperienceRates(node, user) {
  460. const purity = getNodePurityPercentage(node, user);
  461. const density = getNodeDensityPercentage(node, user);
  462. const experience = getNodeExperience(node, density, user);
  463. const ore = 16;
  464. const experienceRate = experience?.toFixed(2);
  465. const oreRate = getOreRate(density, purity, ore);
  466.  
  467. node.children[0].children[0].setAttribute('data-after', `${experienceRate} xp/h ${oreRate} ore/h`);
  468. }
  469.  
  470. function getNodePurityPercentage(node, user) {
  471. const column = node.children[0].children[0].children[2];
  472. if (!column || !column.querySelectorAll('.font-bold')[0]) return;
  473. const label = column.querySelectorAll('.font-bold')[0].parentElement;
  474. let percentage = Number(label.innerText.replace('%','').split(':')[1]);
  475.  
  476. let miningLevel = getMiningLevel(user);
  477. percentage = percentage + (miningLevel * 0.1);
  478.  
  479. return percentage;
  480. }
  481.  
  482. function getNodeDensityPercentage(node, user) {
  483. const column = node.children[0].children[0].children[1];
  484. if (!column || !column.querySelectorAll('.font-bold')[0]) return;
  485. const label = column.querySelectorAll('.font-bold')[0].parentElement;
  486. let percentage = Number(label.innerText.replace('%','').split(':')[1]);
  487.  
  488. let miningLevel = getMiningLevel(user);
  489. percentage = percentage + (miningLevel * 0.1);
  490.  
  491. return percentage;
  492. }
  493.  
  494. function getNodeExperience(node, density, user) {
  495. /** Gets the Exp: label */
  496. const column = node.children[0].children[0].children[3];
  497. if (!column || !column.querySelectorAll('.font-bold')[0]) return;
  498. const label = column.querySelectorAll('.font-bold')[0].parentElement;
  499. let value = Number(label.innerText.replace('%','').split(':')[1]);
  500.  
  501. const skilledExtraction = getSkilledExtractionLevel(user);
  502. const knowledgeExtraction = getKnowledgeExtractionLevel(user) / 100;
  503.  
  504. const ore = getActiveMining();
  505. const expertiseLevel = getExpertiseLevel(user);
  506. const expertiseGain = getExpertiseXPGainByOre(ore);
  507. const additionalMiningGain = expertiseLevel / 5 * expertiseGain;
  508. console.log('additionalMiningGain:', additionalMiningGain);
  509.  
  510. value += additionalMiningGain;
  511.  
  512. const actionsPerHour = getActionsPerHour(density);
  513. const experienceBase = value * actionsPerHour;
  514. const experienceSkilled = actionsPerHour * skilledExtraction;
  515. const experienceKnowledge = value * knowledgeExtraction;
  516.  
  517. value = experienceBase + experienceSkilled + experienceKnowledge;
  518.  
  519. const vip = isUserVIP(user);
  520.  
  521. value *= vip ? 1.1 : 1;
  522.  
  523. return value;
  524. }
  525.  
  526. function getActionsPerHour(density) {
  527. return 3600 / (60 / (density / 100))
  528. }
  529.  
  530. function getExperienceRate(density, experience) {
  531. return Number((3600 / (60 / (density / 100)) * experience).toFixed(2));
  532. }
  533.  
  534. function getOreRate(density, purity, ore) {
  535. return Number((3600 / (60 / (density / 100)) * (purity / 100) * ore).toFixed(2));
  536. }
  537. }
  538.  
  539. function isUserVIP(user) {
  540. return new Date(user.vip_expires) > new Date();
  541. }
  542.  
  543. updateExperienceRates();
  544. window.elethorResourceInterval = setInterval(updateExperienceRates, 500);
  545. initializeResourceNodeView();
  546.  
  547. async function initializeResourceNodeView() {
  548. await waitForField(document, 'head');
  549.  
  550. let css = '[data-after]::after { content: attr(data-after); padding: 12px;}';
  551.  
  552. appendCSS(css);
  553. }
  554. })();
  555.  
  556. async function loadMarketRecyclobotVisualizer() {
  557. if (!window.egpItemPoints) {
  558. const companions = await getCompanions();
  559. const recyclobot = companions.recyclobot;
  560. window.egpItemPoints = recyclobot.itemPoints;
  561.  
  562. console.log(`[${moduleName} v${version}] Companion Data initialized.`);
  563. }
  564.  
  565. window.elethorGeneralPurposeOnLocationChange.addEventListener('EGPLocation', async function (e) {
  566. if (e && e.newLocation) {
  567. window.egpRecyclobotOnMarket = e.newLocation.includes('/market/');
  568.  
  569. if (!window.egpRecyclobotOnMarket) {
  570. return;
  571. }
  572.  
  573. if (!window.egpItemPoints) {
  574. console.warn(`[${moduleName} v${version}] Entered market page but had no recyclobot item points list.`);
  575. return;
  576. }
  577.  
  578. const marketObjects = getMarketListingObjects();
  579.  
  580. for (const marketListing of marketObjects) {
  581. for (const recyclobotEntry of window.egpItemPoints) {
  582. if (recyclobotEntry.item_name === marketListing.name) {
  583. //marketListing.element.setAttribute('data-points-recyclobot',`R: ${recyclobotEntry.quantity}:${recyclobotEntry.points}`);
  584. break;
  585. }
  586. }
  587. }
  588. }
  589. });
  590.  
  591. function getMarketListingObjects() {
  592. return Array.prototype.slice.call(document.querySelectorAll('.is-market-side-menu ul li')).map((e) => {return {element: e, name: e.innerText}});
  593. }
  594.  
  595. async function getCompanions() {
  596. return (await window.axios.get('/game/companions')).data;
  597. }
  598. };
  599.  
  600. function setPackAmount(amount) {
  601. window.packAmount = amount;
  602.  
  603. if (window.packAmount < 0) {
  604. window.packAmount = 0;
  605. }
  606. }
  607.  
  608. window.setPackAmount = setPackAmount;
  609.  
  610. function formatNormalNumber(num){
  611. return num.toLocaleString();
  612. }
  613.  
  614. function appendCSS(css) {
  615. let head = document.head || document.getElementsByTagName('head')[0];
  616. let style = document.createElement('style');
  617.  
  618. head.appendChild(style);
  619.  
  620. style.type = 'text/css';
  621. if (style.styleSheet){
  622. // This is required for IE8 and below.
  623. style.styleSheet.cssText = css;
  624. } else {
  625. style.appendChild(document.createTextNode(css));
  626. }
  627. }
  628.  
  629. function getMiningLevel(user) {
  630. for (const skill of user.skills) {
  631. if (skill.id === 1) {
  632. return skill.pivot.level;
  633. }
  634. }
  635.  
  636. return 0;
  637. }
  638.  
  639. function getSkilledExtractionLevel(user) {
  640. for (const skill of user.skills) {
  641. if (skill.id === 17) {
  642. return skill.pivot.level;
  643. }
  644. }
  645.  
  646. return 0;
  647. }
  648.  
  649. function getKnowledgeExtractionLevel(user) {
  650. for (const skill of user.skills) {
  651. if (skill.id === 18) {
  652. return skill.pivot.level;
  653. }
  654. }
  655.  
  656. return 0;
  657. }
  658.  
  659. function getExpertiseLevel(user) {
  660. for (const skill of user.skills) {
  661. if (skill.id === 38) {
  662. return skill.pivot.level;
  663. }
  664. }
  665.  
  666. return 0;
  667. }
  668.  
  669. expertiseXPGainByOre = {
  670. 'Orthoclase': 780,
  671. 'Anorthite': 825,
  672. 'Ferrisum': 600,
  673. 'Rhenium': 465,
  674. 'Jaspil': 480,
  675. 'Skasix': 0,
  676. };
  677.  
  678. function getExpertiseXPGainByOre(ore) {
  679. return expertiseXPGainByOre[ore];
  680. }
  681.  
  682. function getActiveMining() {
  683. const activeMiningButtons = document.querySelectorAll('.buttons .button.is-success');
  684.  
  685. if (!activeMiningButtons[0] ||
  686. !activeMiningButtons[0].children[0] ||
  687. !activeMiningButtons[0].children[0].innerText ||
  688. !activeMiningButtons[0].children[0].innerText.trim
  689. ) {
  690. return '';
  691. }
  692.  
  693. return activeMiningButtons[0].children[0].innerText.trim();
  694. }
  695.  
  696. function getPackLevel(user) {
  697. for (const skill of user.skills) {
  698. if (skill.id === 22) {
  699. return skill.pivot.level;
  700. }
  701. }
  702.  
  703. return 0;
  704. }
  705.  
  706. function getCarnageLevel(user) {
  707. for (const skill of user.skills) {
  708. if (skill.id === 23) {
  709. return skill.pivot.level;
  710. }
  711. }
  712.  
  713. return 0;
  714. }
  715.  
  716. function getAttackSpeed(speed) {
  717. return 50 - (50 * speed / (speed + 400));
  718. }
  719.  
  720. function getHealth(fortitude) {
  721. return 100000 * fortitude / (fortitude + 4000);
  722. }
  723.  
  724. async function waitForField(target, field) {
  725. return new Promise((resolve, reject) => {
  726. const interval = setInterval(() => {
  727. if (target[field] !== undefined) {
  728. clearInterval(interval);
  729. resolve();
  730. }
  731. }, 100);
  732. });
  733. }
  734.  
  735. async function waitForUser() {
  736. return new Promise((resolve, reject) => {
  737. const interval = setInterval(() => {
  738. if (currentUserData.user && currentUserData.user.id !== undefined) {
  739. clearInterval(interval);
  740. resolve();
  741. }
  742. }, 100);
  743. });
  744. }
  745.  
  746. (async function loadAlarms() {
  747. await waitForField(window, 'Echo');
  748. await waitForField(document, 'head');
  749. await waitForUser();
  750.  
  751.  
  752. const meta1 = document.createElement('meta');
  753. meta1.setAttribute('content', "media-src https://furious.no/elethor/sound/");
  754. meta1.setAttribute('http-equiv', 'Content-Security-Policy');
  755. document.getElementsByTagName('head')[0].appendChild(meta1);
  756.  
  757. /**
  758. * You can add an alternative audio by setting the local storage keys:
  759. * localStorage.setItem('egpSoundOutOfActions', 'your-url')
  760. * localStorage.setItem('egpSoundQuestCompleted', 'your-url')
  761. * */
  762.  
  763. // Alternative: https://furious.no/elethor/sound/elethor-actions-have-ran-out.mp3
  764. window.egpSoundOutOfActions =
  765. localStorage.getItem('egpSoundOutOfActions') || 'https://furious.no/elethor/sound/out-of-actions.wav';
  766.  
  767. // Alternative: https://furious.no/elethor/sound/elethor-quest-completed.mp3
  768. window.egpSoundQuestCompleted =
  769. localStorage.getItem('egpSoundQuestCompleted') || 'https://furious.no/elethor/sound/quest-complete.wav';
  770.  
  771. const masterAudioLevel = 0.3;
  772.  
  773. window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\Monster\\Events\\FightingAgain", handleActionData);
  774. window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\ResourceNode\\Events\\GatheringAgain", handleActionData);
  775. window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\Quest\\Events\\UpdateQuestProgress", handleQuestData);
  776.  
  777. let hasWarnedAboutActions = false;
  778. let hasWarnedAboutQuest = false;
  779.  
  780. /**
  781. * Warns once when action runs out.
  782. * */
  783. function handleActionData(data) {
  784. if (data && data.action) {
  785. if (data.action.remaining <= 0 && !hasWarnedAboutActions) {
  786. playOutOfActions();
  787. hasWarnedAboutActions = true;
  788. } else if (data.action.remaining > 0 && hasWarnedAboutActions) {
  789. hasWarnedAboutActions = false;
  790. }
  791. }
  792. }
  793. window.handleActionData = handleActionData;
  794.  
  795. /**
  796. * Warns once when quest completes.
  797. * */
  798. function handleQuestData(data) {
  799. if (data
  800. && data.step
  801. && data.step.tasks) {
  802. if (data.step.progress >= data.step.tasks[0].quantity && !hasWarnedAboutQuest) {
  803. playQuestCompleted();
  804. hasWarnedAboutQuest = true;
  805. } else if (data.step.progress < data.step.tasks[0].quantity && hasWarnedAboutQuest) {
  806. hasWarnedAboutQuest = false;
  807. }
  808. }
  809. }
  810. window.handleQuestData = handleQuestData;
  811.  
  812. function playOutOfActions() {
  813. playSound(window.egpSoundOutOfActions);
  814. }
  815. window.playOutOfActions = playOutOfActions;
  816.  
  817. function playQuestCompleted() {
  818. playSound(window.egpSoundQuestCompleted);
  819. }
  820. window.playQuestCompleted = playQuestCompleted;
  821.  
  822. function playSound(sound, volume = masterAudioLevel){
  823. const audio = new Audio(sound);
  824.  
  825. audio.volume = volume;
  826. audio.play();
  827. }
  828. })();
  829. })();