Elethor General Purpose

Provides some general additions to Elethor

当前为 2021-06-30 提交的版本,查看 最新版本

  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.7
  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.7';
  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-skill div>span.has-text-weight-bold');
  268. for (const skillElement of skillElements) {
  269. if (skillElement.innerText.includes('Combat')) {
  270. return skillElement;
  271. }
  272. }
  273. }
  274.  
  275. function getProfileMiningElement() {
  276. const skillElements = document.querySelectorAll('.is-skill div>span.has-text-weight-bold');
  277. for (const skillElement of skillElements) {
  278. if (skillElement.innerText.includes('Mining')) {
  279. return skillElement;
  280. }
  281. }
  282. }
  283.  
  284. function updateXPTracker(difference) {
  285. const combatElement = getProfileCombatElement();
  286. if (combatElement) {
  287. if (difference.combat > 0) {
  288. combatElement.setAttribute('data-combat-experience-ahead', `(+${formatNormalNumber(difference.combat)})`);
  289. combatElement.setAttribute('style', `color:lime`);
  290. } else {
  291. combatElement.setAttribute('data-combat-experience-ahead', `(${formatNormalNumber(difference.combat)})`);
  292. combatElement.setAttribute('style', `color:red`);
  293. }
  294. }
  295.  
  296. const miningElement = getProfileMiningElement();
  297. if (difference.mining > 0) {
  298. miningElement.setAttribute('data-mining-experience-ahead', `(+${formatNormalNumber(difference.mining)})`);
  299. miningElement.setAttribute('style', `color:lime`);
  300. } else {
  301. miningElement.setAttribute('data-mining-experience-ahead', `(${formatNormalNumber(difference.mining)})`);
  302. miningElement.setAttribute('style', `color:red`);
  303. }
  304. }
  305.  
  306. function initializeProfileLoadListener() {
  307. let css = '[data-combat-experience-ahead]::after { content: attr(data-combat-experience-ahead); padding: 12px;}';
  308. css += '[data-mining-experience-ahead]::after { content: attr(data-mining-experience-ahead); padding: 12px;}';
  309.  
  310. appendCSS(css);
  311.  
  312. window.elethorGeneralPurposeOnLocationChange.addEventListener('EGPLocation', async function (e) {
  313. if (e && e.newLocation) {
  314. if(e.newLocation.includes('/profile/')) {
  315. console.log('Profile view detected:', e);
  316. const url = e.newLocation;
  317. const path = url.substr(url.indexOf(profileURL));
  318.  
  319. // We know we have a profile lookup, and not user-data load if the length differs.
  320. if (path.length > profileURL.length) {
  321. const userId = Number(path.substr(path.lastIndexOf('/') + 1));
  322.  
  323. const difference = await getExperienceDifference(userId, currentUserData.user.id);
  324.  
  325. updateXPTracker(difference);
  326. }
  327. }
  328. }
  329. });
  330.  
  331. console.log(`[${moduleName} v${version}] Profile Load Listener initialized.`);
  332. }
  333.  
  334. async function getUser(id) {
  335. const result = await window.axios.get(`/game/user/${id}?egpIgnoreMe=true`);
  336. return result.data;
  337. }
  338.  
  339. window.getUser = getUser;
  340.  
  341. async function getUserSelf() {
  342. const result = await window.axios.get(`/game/user`);
  343. return result.data;
  344. }
  345.  
  346. window.getUserSelf = getUserSelf;
  347.  
  348. async function getUserStats() {
  349. const result = await window.axios.get(`/game/inventory/stats`);
  350. return result.data;
  351. }
  352.  
  353. window.getUserStats = getUserStats;
  354.  
  355. async function getUserStatsJSON(pretty) {
  356. const stats = await getUserStats();
  357.  
  358. if (pretty) {
  359. return JSON.stringify(stats, null, 2);
  360. }
  361.  
  362. return JSON.stringify(stats);
  363. }
  364.  
  365. window.getUserStatsJSON = getUserStatsJSON;
  366.  
  367. function getUserCombatStats(user) {
  368. for (const skill of user.skills) {
  369. if (skill.name === 'combat') {
  370. return skill.pivot;
  371. }
  372. }
  373. }
  374.  
  375. function getMiningStats(user) {
  376. for (const skill of user.skills) {
  377. if (skill.id === 1) {
  378. return skill.pivot;
  379. }
  380. }
  381.  
  382. return 0;
  383. }
  384.  
  385. async function getExperienceDifference(userId1, userId2) {
  386. const [user1, user2] = await Promise.all([
  387. getUser(userId1),
  388. getUser(userId2)
  389. ]);
  390.  
  391. const combatStats1 = getUserCombatStats(user1);
  392. const miningStats1 = getMiningStats(user1);
  393.  
  394. const combatStats2 = getUserCombatStats(user2);
  395. const miningStats2 = getMiningStats(user2);
  396.  
  397. return {
  398. combat: combatStats2.experience - combatStats1.experience,
  399. mining: miningStats2.experience - miningStats1.experience,
  400. };
  401. }
  402.  
  403. (async function run() {
  404. await waitForField(window, 'axios');
  405. forceLoadUser();
  406. initializeToastKiller();
  407. initializeXHRHook();
  408. initializeUserLoadListener();
  409. initializeInventoryStatsLoadListener();
  410. initializeLocationChangeListener();
  411. initializeProfileLoadListener();
  412. initializePackStats();
  413.  
  414. console.log(`[${moduleName} v${version}] Loaded.`);
  415. })();
  416.  
  417. (async function loadRerollDisableButtonModule() {
  418. async function waitForEcho() {
  419. return new Promise((resolve, reject) => {
  420. const interval = setInterval(() => {
  421. if (window.Echo) {
  422. clearInterval(interval);
  423. resolve();
  424. }
  425. }, 100);
  426. });
  427. }
  428.  
  429. await waitForEcho();
  430. await waitForUser();
  431.  
  432. elethorGeneralPurposeOnXHR.addEventListener('EGPXHR', async function (e) {
  433. if (e && e.xhr && e.xhr.responseURL) {
  434. if(e.xhr.responseURL.includes('/game/energize')) {
  435. const itemID = e.xhr.responseURL.substr(e.xhr.responseURL.lastIndexOf('/')+1);
  436. window.lastEnergizeID = Number(itemID);
  437. }
  438. }
  439. });
  440. })();
  441.  
  442. (async function loadResourceNodeUpdater() {
  443. await waitForField(currentUserData, 'user');
  444. const user = await getUser(currentUserData.user.id);
  445.  
  446. function updateExperienceRates() {
  447. document.querySelectorAll('.is-resource-node').forEach(async (node) => {
  448. visualizeResourceNodeExperienceRates(node, user)
  449. });
  450.  
  451. function visualizeResourceNodeExperienceRates(node, user) {
  452. const purity = getNodePurityPercentage(node, user);
  453. const density = getNodeDensityPercentage(node, user);
  454. const experience = getNodeExperience(node, density, user);
  455. const ore = 16;
  456. const experienceRate = experience.toFixed(2);
  457. const oreRate = getOreRate(density, purity, ore);
  458.  
  459. node.children[0].setAttribute('data-after', `${experienceRate} xp/h ${oreRate} ore/h`);
  460. }
  461.  
  462. function getNodePurityPercentage(node, user) {
  463. const column = node.children[0].children[2];
  464. const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
  465. let percentage = Number(label.innerText.replace('%','').split(':')[1]);
  466.  
  467. let miningLevel = getMiningLevel(user);
  468. percentage = percentage + (miningLevel * 0.1);
  469.  
  470. return percentage;
  471. }
  472.  
  473. function getNodeDensityPercentage(node, user) {
  474. const column = node.children[0].children[1];
  475. const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
  476. let percentage = Number(label.innerText.replace('%','').split(':')[1]);
  477.  
  478. let miningLevel = getMiningLevel(user);
  479. percentage = percentage + (miningLevel * 0.1);
  480.  
  481. return percentage;
  482. }
  483.  
  484. function getNodeExperience(node, density, user) {
  485. const column = node.children[0].children[3];
  486. const label = column.getElementsByClassName('has-text-weight-bold')[0].parentElement;
  487. let value = Number(label.innerText.replace('%','').split(':')[1]);
  488.  
  489. const skilledExtraction = getSkilledExtractionLevel(user);
  490. const knowledgeExtraction = getKnowledgeExtractionLevel(user) / 100;
  491.  
  492. const actionsPerHour = getActionsPerHour(density);
  493. const experienceBase = value * actionsPerHour;
  494. const experienceSkilled = actionsPerHour * skilledExtraction;
  495. const experienceKnowledge = value * knowledgeExtraction;
  496.  
  497. value = experienceBase + experienceSkilled + experienceKnowledge;
  498.  
  499. const vip = isUserVIP(user);
  500.  
  501. value *= vip ? 1.1 : 1;
  502.  
  503. return value;
  504. }
  505.  
  506. function getActionsPerHour(density) {
  507. return 3600 / (60 / (density / 100))
  508. }
  509.  
  510. function getExperienceRate(density, experience) {
  511. return Number((3600 / (60 / (density / 100)) * experience).toFixed(2));
  512. }
  513.  
  514. function getOreRate(density, purity, ore) {
  515. return Number((3600 / (60 / (density / 100)) * (purity / 100) * ore).toFixed(2));
  516. }
  517. }
  518.  
  519. function isUserVIP(user) {
  520. return new Date(user.vip_expires) > new Date();
  521. }
  522.  
  523. updateExperienceRates();
  524. window.elethorResourceInterval = setInterval(updateExperienceRates, 500);
  525. initializeResourceNodeView();
  526.  
  527. async function initializeResourceNodeView() {
  528. await waitForField(document, 'head');
  529.  
  530. let css = '[data-after]::after { content: attr(data-after); padding: 12px;}';
  531.  
  532. appendCSS(css);
  533. }
  534. })();
  535.  
  536. async function initializePackStats() {
  537. await waitForField(document, 'head');
  538. await waitForUser();
  539.  
  540. function loadPackStatsCSS() {
  541. const css = '' +
  542. '[data-pack-stat-ATTACK-SPEED]::after {content: attr(data-pack-stat-ATTACK-SPEED);float: right;}' +
  543. '[data-pack-stat-HEALTH]::after {content: attr(data-pack-stat-HEALTH);float: right;}' +
  544. '[data-pack-stat-FORTITUDE]::after {content: attr(data-pack-stat-FORTITUDE);float: right;}' +
  545. '[data-pack-stat-SPEED]::after {content: attr(data-pack-stat-SPEED);float: right;}' +
  546. '[data-pack-stat-SAVAGERY]::after {content: attr(data-pack-stat-SAVAGERY);float: right;}' +
  547. '[data-pack-stat-PIERCE]::after {content: attr(data-pack-stat-PIERCE);float: right;}' +
  548. '[data-pack-stat-ARMOR]::after {content: attr(data-pack-stat-ARMOR);float: right;}' +
  549. '[data-pack-stat-DAMAGE-REDUCTION]::after {content: attr(data-pack-stat-DAMAGE-REDUCTION);float: right;}'
  550. ;
  551.  
  552. appendCSS(css);
  553. }
  554. loadPackStatsCSS();
  555.  
  556. function getMonsterStatSpans() {
  557. return document.querySelectorAll('.is-desktop-monster-stats table tr td:last-child>span');
  558. }
  559.  
  560. function applyPackStats(packAmount) {
  561. const statSpans = getMonsterStatSpans();
  562.  
  563. let counter = 0;
  564.  
  565. let attackSpeedSpan, healthSpan;
  566.  
  567. for (const span of statSpans) {
  568. // Save attack speed span for later when we get speed stat
  569. if (counter === 0) {
  570. counter++;
  571. attackSpeedSpan = span;
  572. continue;
  573. }
  574.  
  575. // Save health span for later when we get fort stat
  576. if (counter === 1) {
  577. counter++;
  578. healthSpan = span;
  579. continue;
  580. }
  581.  
  582. const stat = processSpan(span);
  583.  
  584. // Health
  585. if (counter === 2) {
  586. processSpan(healthSpan, stat, 'health');
  587. }
  588.  
  589. // Speed
  590. if (counter === 3) {
  591. processSpan(attackSpeedSpan, stat, 'speed');
  592. }
  593.  
  594. counter++;
  595. }
  596.  
  597. function processSpan(span, statOverride, overrideType) {
  598. let stat = Number(span.innerText.replace('%', '').replace(',', '').replace(',', '').replace(',', ''));
  599.  
  600. const original10Percent = stat * 0.1;
  601. for (let i = 0; i < packAmount; i++) {
  602. stat += original10Percent;
  603. }
  604. stat = Math.floor(stat);
  605.  
  606. if (statOverride) {
  607. stat = statOverride;
  608.  
  609. if (overrideType === 'speed') {
  610. stat = getAttackSpeed(stat);
  611. }
  612.  
  613. if (overrideType === 'health') {
  614. stat = getHealth(stat);
  615. }
  616. }
  617.  
  618. let statPackFormatted;
  619.  
  620. if (span.innerText.includes('%')) {
  621. statPackFormatted = `${stat.toFixed(3)}%`;
  622. } else {
  623. statPackFormatted = Math.floor(stat).toFixed(0);
  624. }
  625.  
  626. statPackFormatted = statPackFormatted.toLocaleString();
  627.  
  628. span.setAttribute('data-pack-stat-' + span.parentElement.parentElement.children[0].innerText.replace(' ', '-'), `${statPackFormatted} (${packAmount+1}x)`);
  629.  
  630. return stat;
  631. }
  632. }
  633.  
  634. loadPackStatsCSS();
  635.  
  636. const multiplierByPackLevel = {
  637. 0: 3,
  638. 1: 4,
  639. 2: 5,
  640. 3: 6,
  641. 4: 8,
  642. 5: 10
  643. }
  644.  
  645. const multiplierByCarnageLevel = {
  646. 1: 11,
  647. 2: 12,
  648. 3: 13,
  649. 4: 14,
  650. 5: 15
  651. }
  652.  
  653. const packStatsUser = await getUser(currentUserData.user.id);
  654. const packLevel = getPackLevel(packStatsUser);
  655. const carnageLevel = getCarnageLevel(packStatsUser);
  656.  
  657. let packAmount = multiplierByPackLevel[packLevel];
  658.  
  659. if (carnageLevel > 0) {
  660. packAmount = multiplierByCarnageLevel[carnageLevel];
  661. }
  662.  
  663. setPackAmount(packAmount-1);
  664.  
  665. window.elethorPackStatsInterval = setInterval(() => {
  666. applyPackStats(window.packAmount);
  667. }, 500);
  668.  
  669. document.addEventListener('click', function(e) {
  670. if (e.target
  671. && e.target.id
  672. && (e.target.id === 'egpPackButtonUp' || e.target.id === 'egpPackButtonDown')
  673. ) {
  674. if (e.target.id === 'egpPackButtonUp') {
  675. setPackAmount(window.packAmount + 1);
  676. } else {
  677. setPackAmount(window.packAmount - 1);
  678. }
  679.  
  680. applyPackStats(window.packAmount);
  681. }
  682. });
  683.  
  684. function addPackChangeButtons() {
  685. if (document.querySelector('#egpPackButtonUp')) { return; }
  686.  
  687. const table = document.querySelector('.is-desktop-monster-stats table');
  688.  
  689. if (!table) { return; }
  690.  
  691. const buttonUp = document.createElement('button');
  692. buttonUp.setAttribute('style', 'float: right; height: 25px; width: 25px;');
  693. buttonUp.innerText = '→';
  694. buttonUp.id ='egpPackButtonUp';
  695. buttonUp.className ='button egpButton';
  696.  
  697. const buttonDown = document.createElement('button');
  698. buttonDown.setAttribute('style', 'float: right; height: 25px; width: 25px;');
  699. buttonDown.innerText = '←';
  700. buttonDown.id ='egpPackButtonDown';
  701. buttonDown.className ='button egpButton';
  702.  
  703. table.parentElement.insertBefore(buttonUp, table);
  704. table.parentElement.insertBefore(buttonDown, table);
  705. }
  706. addPackChangeButtons();
  707.  
  708. window.elethorPackStatsButtonInterval = setInterval(addPackChangeButtons, 500);
  709. }
  710.  
  711. function setPackAmount(amount) {
  712. window.packAmount = amount;
  713.  
  714. if (window.packAmount < 0) {
  715. window.packAmount = 0;
  716. }
  717. }
  718.  
  719. window.setPackAmount = setPackAmount;
  720.  
  721. function formatNormalNumber(num){
  722. return num.toLocaleString();
  723. }
  724.  
  725. function appendCSS(css) {
  726. let head = document.head || document.getElementsByTagName('head')[0];
  727. let style = document.createElement('style');
  728.  
  729. head.appendChild(style);
  730.  
  731. style.type = 'text/css';
  732. if (style.styleSheet){
  733. // This is required for IE8 and below.
  734. style.styleSheet.cssText = css;
  735. } else {
  736. style.appendChild(document.createTextNode(css));
  737. }
  738. }
  739.  
  740. function getMiningLevel(user) {
  741. for (const skill of user.skills) {
  742. if (skill.id === 1) {
  743. return skill.pivot.level;
  744. }
  745. }
  746.  
  747. return 0;
  748. }
  749.  
  750. function getSkilledExtractionLevel(user) {
  751. for (const skill of user.skills) {
  752. if (skill.id === 17) {
  753. return skill.pivot.level;
  754. }
  755. }
  756.  
  757. return 0;
  758. }
  759.  
  760. function getKnowledgeExtractionLevel(user) {
  761. for (const skill of user.skills) {
  762. if (skill.id === 18) {
  763. return skill.pivot.level;
  764. }
  765. }
  766.  
  767. return 0;
  768. }
  769.  
  770. function getPackLevel(user) {
  771. for (const skill of user.skills) {
  772. if (skill.id === 22) {
  773. return skill.pivot.level;
  774. }
  775. }
  776.  
  777. return 0;
  778. }
  779.  
  780. function getCarnageLevel(user) {
  781. for (const skill of user.skills) {
  782. if (skill.id === 23) {
  783. return skill.pivot.level;
  784. }
  785. }
  786.  
  787. return 0;
  788. }
  789.  
  790. function getAttackSpeed(speed) {
  791. return 50 - (50 * speed / (speed + 400));
  792. }
  793.  
  794. function getHealth(fortitude) {
  795. return 100000 * fortitude / (fortitude + 4000);
  796. }
  797.  
  798. async function waitForField(target, field) {
  799. return new Promise((resolve, reject) => {
  800. const interval = setInterval(() => {
  801. if (target[field] !== undefined) {
  802. clearInterval(interval);
  803. resolve();
  804. }
  805. }, 100);
  806. });
  807. }
  808.  
  809. async function waitForUser() {
  810. return new Promise((resolve, reject) => {
  811. const interval = setInterval(() => {
  812. if (currentUserData.user && currentUserData.user.id !== undefined) {
  813. clearInterval(interval);
  814. resolve();
  815. }
  816. }, 100);
  817. });
  818. }
  819.  
  820. (async function loadAlarms() {
  821. await waitForField(window, 'Echo');
  822. await waitForField(document, 'head');
  823. await waitForUser();
  824. const meta1 = document.createElement('meta');
  825. meta1.setAttribute('content', "media-src https://furious.no/elethor/sound/");
  826. meta1.setAttribute('http-equiv', 'Content-Security-Policy');
  827. document.getElementsByTagName('head')[0].appendChild(meta1);
  828.  
  829. /**
  830. * You can add an alternative audio by setting the local storage keys:
  831. * localStorage.setItem('egpSoundOutOfActions', 'your-url')
  832. * localStorage.setItem('egpSoundQuestCompleted', 'your-url')
  833. * */
  834.  
  835. // Alternative: https://furious.no/elethor/sound/elethor-actions-have-ran-out.mp3
  836. window.egpSoundOutOfActions =
  837. localStorage.getItem('egpSoundOutOfActions') || 'https://furious.no/elethor/sound/out-of-actions.wav';
  838.  
  839. // Alternative: https://furious.no/elethor/sound/elethor-quest-completed.mp3
  840. window.egpSoundQuestCompleted =
  841. localStorage.getItem('egpSoundQuestCompleted') || 'https://furious.no/elethor/sound/quest-complete.wav';
  842.  
  843. const masterAudioLevel = 0.3;
  844.  
  845. window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\Monster\\Events\\FightingAgain", handleActionData);
  846. window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\ResourceNode\\Events\\GatheringAgain", handleActionData);
  847. window.Echo.private(`App.User.${currentUserData.user.id}`).listen(".App\\Domain\\Quest\\Events\\UpdateQuestProgress", handleQuestData);
  848.  
  849. let hasWarnedAboutActions = false;
  850. let hasWarnedAboutQuest = false;
  851.  
  852. /**
  853. * Warns once when action runs out.
  854. * */
  855. function handleActionData(data) {
  856. if (data && data.action) {
  857. if (data.action.remaining <= 0 && !hasWarnedAboutActions) {
  858. playOutOfActions();
  859. hasWarnedAboutActions = true;
  860. } else if (data.action.remaining > 0 && hasWarnedAboutActions) {
  861. hasWarnedAboutActions = false;
  862. }
  863. }
  864. }
  865. window.handleActionData = handleActionData;
  866.  
  867. /**
  868. * Warns once when quest completes.
  869. * */
  870. function handleQuestData(data) {
  871. if (data
  872. && data.step
  873. && data.step.tasks) {
  874. if (data.step.progress >= data.step.tasks[0].quantity && !hasWarnedAboutQuest) {
  875. playQuestCompleted();
  876. hasWarnedAboutQuest = true;
  877. } else if (data.step.progress < data.step.tasks[0].quantity && hasWarnedAboutQuest) {
  878. hasWarnedAboutQuest = false;
  879. }
  880. }
  881. }
  882. window.handleQuestData = handleQuestData;
  883.  
  884. function playOutOfActions() {
  885. playSound(window.egpSoundOutOfActions);
  886. }
  887. window.playOutOfActions = playOutOfActions;
  888.  
  889. function playQuestCompleted() {
  890. playSound(window.egpSoundQuestCompleted);
  891. }
  892. window.playQuestCompleted = playQuestCompleted;
  893.  
  894. function playSound(sound, volume = masterAudioLevel){
  895. const audio = new Audio(sound);
  896.  
  897. audio.volume = volume;
  898. audio.play();
  899. }
  900. })();
  901. })();